std::make_unique()
(和类似的函数)有一点problem:
#include <cstdio>
#include <memory>
using namespace std;
struct S
{
S() { printf("ctor\n"); }
~S() { printf("dtor\n"); }
S(S const&) { printf("cctor\n"); }
S(S&&) { printf("mctor\n"); }
};
S foo() { return S(); }
int main()
{
{
printf("--------------- case 1 ---------------\n");
unique_ptr<S> s1 = make_unique<S>( foo() );
}
{
printf("--------------- case 2 ---------------\n");
unique_ptr<S> s2 { new S( foo() ) };
}
}
输出:
--------------- case 1 ---------------
ctor
mctor
dtor
dtor
--------------- case 2 ---------------
ctor
dtor
如你所见,我们有一个可以避免的额外举动。 emplace()
在optional / variant / etc中存在Same问题 - 如果对象被其他函数返回,则必须移动它。
这可以addressed有一个技巧:
#include <cstdio>
#include <optional>
using namespace std;
struct S
{
S() { printf("ctor\n"); }
~S() { printf("dtor\n"); }
S(S const&) { printf("cctor\n"); }
S(S&&) { printf("mctor\n"); }
template<class F, enable_if_t<is_same_v<invoke_result_t<F>, S>>...>
S(F&& f) : S(forward<F>(f)()) {}
};
S foo() { return S(); }
int main()
{
optional<S> s;
s.emplace( []{ return foo(); } );
}
这可以避免不必要的移动(enable_if隐藏构造函数,除非f()
返回S的实例)。通过调用构造函数,您最终有效地在std::variant
/ std::optional
/ etc中构建了值。
此修复有一点问题 - 添加构造函数会破坏聚合初始化。见example。即如果给定的结构没有构造函数并且你添加一个 - 你不能再像这样初始化它:
struct D
{
float m;
S s;
// adding new constructor here will break existing bar() functions
};
D bar() { /*...lots of code with multiple return statements...*/ return {2.0, foo()}; }
问题:有没有解决这个问题的方法?没有引入新构造函数的东西......
我希望能够有效地将我的结构放入optional / variant / shared_ptr-block / etc而不会破坏(而非重要的)创建它们的代码。
答案 0 :(得分:10)
不是将类型的构造函数添加到带有工厂函数的类型中,而是使用转换运算符创建一个新的外部工厂对象。使用C ++ 17,只需要很少的工作:
template <class F>
struct factory {
F f;
operator invoke_result_t<F&>() { return f(); }
};
template <class F>
factory(F ) -> factory<F>;
对于前面的示例,S
不再需要约束构造函数。你会这样做:
optional<S> s;
s.emplace( factory{[]{ return foo(); }} ); // or really just factory{foo}
仅打印ctor
和dtor
。由于我们不以任何方式修改S
,因此我们也可以在聚合中使用它 - 例如D
。