为什么在C ++ 17中使用std :: make_unique?

时间:2018-12-20 14:23:53

标签: c++ c++17 unique-ptr

据我所知,C ++ 14引入了std::make_unique,因为由于未指定参数求值顺序,因此这是不安全的:

f(std::unique_ptr<MyClass>(new MyClass(param)), g()); // Syntax A

(说明:如果评估首先为原始指针分配内存,然后调用g(),并且在std::unique_ptr构造之前抛出异常,则内存被泄漏。)

呼叫std::make_unique是限制呼叫顺序的一种方法,从而使事情变得安全:

f(std::make_unique<MyClass>(param), g());             // Syntax B

从那时起,C ++ 17阐明了评估顺序,也使语法A变得安全,因此这是我的问题:std::make_unique的上方,是否还有理由使用std::unique_ptr C ++ 17中的构造函数?你能举一些例子吗?

到目前为止,我能想象的唯一原因是它只允许键入MyClass(假设您不需要依靠std::unique_ptr<Base>(new Derived(param))的多态性)。但是,这似乎是一个很弱的原因,尤其是当std::make_unique的构造函数允许std::unique_ptr不允许指定删除程序时。

为了清楚起见,我不主张从标准库中删除std::make_unique(至少保持向后兼容才有意义),而是想知道是否仍然存在这种情况是std::unique_ptr

的首选

6 个答案:

答案 0 :(得分:63)

您说对了,主要原因已删除。仍然有不要使用新的准则,并且键入的原因更少(不必重复键入或使用单词new)。诚然,这些并不是强力的论据,但我真的很喜欢在代码中看不到new

也不要忘记一致性。您绝对应该使用make_shared,因此使用make_unique是自然的并且适合该模式。然后,在语法A需要更多重写的情况下,将std::make_unique<MyClass>(param)更改为std::make_shared<MyClass>(param)(或相反)很简单。

答案 1 :(得分:42)

make_uniqueTT[]T[N]区别开来,unique_ptr(new ...)则没有。

通过将new[]的指针传递到unique_ptr<T>,或通过将new的指针传递到unique_ptr<T[]>,可以轻松地获得未定义的行为。

答案 2 :(得分:20)

原因是代码较短,没有重复。比较

f(std::unique_ptr<MyClass>(new MyClass(param)), g());
f(std::make_unique<MyClass>(param), g());

您保存MyClassnew和花括号。与 ptr 相比,在 make 中仅花费一个字符。

答案 3 :(得分:16)

每次使用new时都必须经过仔细审核,以确保生命周期的正确性;它会被删除吗?只有一次?

每次使用make_unique都不意味着那些额外的特征;只要拥有对象的生存期为“正确”,它就会递归地使唯一指针具有“正确”。

现在,unique_ptr<Foo>(new Foo())确实在所有方面都与make_unique<Foo>()相同 1 ;它只需要一个简单的“为new的所有使用复制您的源代码来审核它们”。


在一般情况下,

1 实际上是一个谎言。完美的转发并不完美,{},默认初始化,数组都是例外。

答案 4 :(得分:0)

  

从那时起,C ++ 17明确了评估顺序,从而使语法A也很安全

那真的不够好。依靠最近引入的技术条款来保证安全并不是一个非常可靠的做法:

  • 有人可能会在C ++ 14中编译此代码。
  • 您会鼓励在其他地方使用原始new,例如通过复制粘贴您的示例。
  • 身为S.M.建议,由于存在代码重复,一种类型可能会更改而另一种类型不会更改。
  • 某些自动IDE重构可能会将new移到其他地方(好的,当然,机会不大)。

通常,您的代码应该适当/健壮/明确有效地 ,而无需借助语言排版,查找标准中的次要或晦涩的技术条款,是个好主意。

(这与我为元组破坏的顺序here所做的论点基本相同。)

答案 5 :(得分:-1)

考虑 无效函数(std :: unique_ptr(new A()),std :: unique_ptr(new B())){...}

假设新的A()成功,但是新的B()抛出异常:您捕获到该异常以恢复程序的正常执行。不幸的是,C ++标准不要求销毁对象A并释放其内存:内存静默泄漏,无法清除它。通过将A和B包装到std :: make_uniques中,可以确保不会发生泄漏:

无效函数(std :: make_unique(),std :: make_unique()){...} 这里的要点是,std :: make_unique和std :: make_unique现在是临时对象,并且在C ++标准中正确指定了临时对象的清除:将触发它们的析构函数并释放内存。因此,如果可以的话,总是喜欢使用std :: make_unique和std :: make_shared分配对象。