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

标签 c++ c++17

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

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

这可以是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 . IE。如果给定的结构没有构造函数并且你添加了一个——你不能再像这样初始化它:

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 中,而不会破坏(相当重要的)创建它们的代码。


编辑:所有 MSVC 版本都无法处理从 Barry 的 factory 转义的异常。查看详情 here .

最佳答案

不要向你的类型添加一个带有工厂函数的构造函数,而是创建一个新的外部工厂对象,其中包含一个转换运算符到你的类型。使用 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}

仅打印 ctordtor。由于我们没有以任何方式修改 S,我们也可以在聚合中使用它——比如 D

关于c++ - 避免在 make_unique/make_shared/emplace/etc 中对使用聚合初始化的结构进行额外移动,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45345175/

相关文章:

c++ - 检测std::shared_ptr持有原始数组(并获取其大小)

C++ 部分特化不适用于不同大小的特征矩阵

c++ - 使用 dll 导出类时 __declspec(dllimport) 的未解析外部符号

c++ - 将 QMainWindow 中的 enterPressed() 连接到 Button clicked()

c++ - 非类型可变模板参数

c++ - 无穷大不是 constexpr

c# - 来自 C# : C++ function (in a DLL) returning false, 的 C++,但 C# 认为这是真的!

java - JNI Eclipse 插件

c++ - 为什么非平凡成员需要在同一类中为匿名 union 定义构造函数

c++ - 我可以写一个类似于缩写函数模板的 catch 子句吗?