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

具体来说:直接列表初始化(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中介绍,为什么它也遵循相同的模式?

最佳答案

Specifically, what pitfalls can be in the list initialization solution?

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

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

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

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

struct Agg
{
  char c;
  int i;
};

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

您需要将该显式转换为char.

标准库有一个问题:LWG 2089.虽然从技术上讲这个问题涉及allocator :: construct,它同样适用于所有间接初始化函数,如make_X和C 17的任何/ optional / variant的就地构造函数.

why does it too follow same pattern?

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

转载注明原文:c – 有没有理由说std :: make_shared / std :: make_unique不使用列表初始化? - 代码日志