对于使用聚合初始化的结构,避免在make_unique/make_shared/emplace/etc中进行额外移动

C.M*_*.M. 14 c++ c++11 c++14 c++17

std::make_unique()(和类似的功能)有一点问题:

#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() ) };
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

--------------- case 1 ---------------
ctor
mctor
dtor
dtor
--------------- case 2 ---------------
ctor
dtor
Run Code Online (Sandbox Code Playgroud)

如你所见,我们有一个可以避免的额外举动.在optional/variant/etc中存在同样的问题emplace()- 如果对象被其他函数返回,则必须移动它.

这可以通过一个技巧来解决:

#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(); } );
}
Run Code Online (Sandbox Code Playgroud)

这避免了不必要的移动(enable_if隐藏构造函数,除非f()返回S的实例).你有效地结束了构建内部的值std::variant/ std::optional通过您的构建函数调用的/ etc.

此修复有一点问题 - 添加构造函数会中断聚合初始化.见例子.即如果给定的结构没有构造函数并且你添加一个 - 你不能再像这样初始化它:

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()}; }
Run Code Online (Sandbox Code Playgroud)

问题:有没有解决这个问题的方法?不引入新构造函数的东西......

我希望能够有效地将我的结构放入optional/variant/shared_ptr-block/etc中,而不会破坏(相当重要)创建它们的代码.

Bar*_*rry 10

而不是将构造函数添加到采用工厂函数的类型中,而是创建一个新的外部工厂对象,并将转换运算符添加到您的类型中.使用C++ 17,只需要最少的工作:

template <class F>
struct factory {
    F f;

    operator invoke_result_t<F&>() { return f(); }
};

template <class F>
factory(F ) -> factory<F>;
Run Code Online (Sandbox Code Playgroud)

对于前面的示例,S不再需要约束构造函数.你会这样做:

optional<S> s;
s.emplace( factory{[]{ return foo(); }} ); // or really just factory{foo}
Run Code Online (Sandbox Code Playgroud)

哪个打印只是ctordtor.由于我们没有S以任何方式进行修改,我们也可以在聚合中使用它 - 比如D.