有没有理由说std :: make_shared / std :: make_unique不使用列表初始化?

时间:2017-01-04 21:54:23

标签: c++ c++11 std shared-ptr

具体来说:直接列表初始化cppreference.com (3))。

C ++ 11 中引入了std::make_shared统一初始化功能。因此,我们可以在堆上分配对象时使用聚合初始化new Foo{1, "2", 3.0f}。这是直接初始化没有构造函数的对象的好方法,例如聚合,pod等。

根据我的经验,现实场景,例如在函数中声明临时结构,有效地为lambda提供一组参数变得非常普遍:

void foo()
{
    struct LambdaArgs
    {
        std::string arg1;
        std::string arg2;
        std::string arg3;
    };

    auto args = std::make_shared<LambdaArgs>(LambdaArgs{"1", "2", "3"});

    auto lambda = [args] {
        /// ...
    };

    /// Use lambda
    /// ...
}

这里auto args = std::make_shared<LambdaArgs>("1", "2", "3");很好但不会起作用,因为std::make_shared通常实现为:

template<typename T, typename... Args>
std::shared_ptr<T> make_shared(Args && ...args)
{
    return std::shared_ptr<T>(new T(std::forward<Args>(args)...));
}

所以我们坚持使用auto args = std::make_shared<LambdaArgs>(LambdaArgs{"1", "2", "3"});

std::make_shared应该解决的问题仍然存在于没有构造函数的对象中。而且解决方法不仅不美观而且效率低下。

这是另一种疏忽还是有一些理由来捍卫这种选择。具体来说,列表初始化解决方案中可能存在哪些陷阱? std::make_unique稍后在 C ++ 14 中介绍,为什么它也遵循相同的模式?

2 个答案:

答案 0 :(得分:5)

  

具体来说,列表初始化解决方案中可能存在哪些陷阱?

使用列表初始化的所有典型缺陷。

例如,隐藏非initializer_list构造函数。 make_shared<vector<int>>(5, 2)做了什么?如果你的答案是“构造一个5 int s的数组”,那就绝对正确......只要make_shared 没有使用列表初始化。因为这会改变你做的那一刻。

请注意,突然改变它会破坏现有代码,因为现在所有间接初始化函数都使用构造函数语法。所以你不能随便改变它,并期望世界继续工作。

另外还有一个独特的案例:缩小问题:

struct Agg
{
  char c;
  int i;
};

您可以Agg a{5, 1020};初始化此聚合。但你永远不能做make_shared<Agg>(5, 1020)。为什么?因为编译器可以保证文字5可以转换为char而不会丢失数据。但是,当您使用这样的间接初始化时,文字5会被模板推导为int。并且编译器不能保证任何int都可以转换为char而不会丢失数据。这称为“缩小转换”,在列表初始化中明确禁止。

您需要将5明确转换为char

标准库存在以下问题:LWG 2089。虽然从技术上讲这个问题涉及allocator::construct,但它同样适用于所有间接初始化函数,如make_X和C ++ 17的any / optional / {的就地构造函数{1}}。

  

为什么它也遵循相同的模式?

它遵循相同的模式,因为具有两个看起来几乎相同且具有根本和意外不同行为的不同功能将不是一件好事。

答案 1 :(得分:1)

  

使用std :: make_shared应该解决的问题仍然存在于没有构造函数的对象中。

不,问题不会持续存在。问题make_shared正在解决的主要问题是分配对象和智能指针所占用的内存泄漏的可能性。它还能够为控制块删除一个额外的分配。

是的,不能使用直接初始化是不方便的,但这绝不是make_shared的声明目标。