使用std::make_unique相比于new运算符的优势

191

使用std::make_unique初始化std::unique_ptr相比使用new运算符有什么优势?

换句话说,为什么

std::unique_ptr<SomeObject> a = std::make_unique(SomeObject(...))
比做更好。
std::unique_ptr<SomeObject> a = new SomeObject(...)

我在网上搜索了很多,知道在现代C++中避免使用运算符new是一个好的经验法则,但我不确定在这种情况下有什么优点。它是否可以防止可能发生的任何内存泄漏?使用std::make_unique是否比使用new更快?


8
实际上,std::make_unique(SomeObject(...)) 调用了构造函数和拷贝/移动构造函数。更好的选择是直接使用底层模板类型的构造函数参数来调用 std::make_unique - stuhlo
2个回答

199

优点

  • make_unique教导用户“永远不要使用new/deletenew[]/delete[]”,无需免责声明。

  • make_uniquemake_shared共享两个优点(不包括第三个优点,即提高效率)。首先,unique_ptr<LongTypeName> up(new LongTypeName(args))必须两次提到LongTypeName,而auto up = make_unique<LongTypeName>(args)只提到一次。

  • make_unique防止由foo(unique_ptr<X>(new X), unique_ptr<Y>(new Y))这样的表达式引起的未指定评估顺序泄漏。(遵循“永远不要使用new”比“永远不要使用new,除非立即将其给命名的unique_ptr”更简单。)

  • make_unique经过精心实现以实现异常安全,并建议使用它来代替直接调用unique_ptr构造函数。

不使用make_unique的情况

  • 如果需要自定义删除器或从其他地方采用原始指针,则不要使用make_unique

来源

  1. 提议std::make_unique
  2. Herb Sutter的GotW #89解决方案:智能指针

1
谢谢你的回答。你能解释一下为什么不使用它的理由吗? - DBedrenko
1
有一个额外的情况不应使用make_unique:在私有或受保护的构造函数的情况下!请不要听取SO或网络上建议使make_unique或unique_ptr或任何其他东西成为您类的朋友。这是非可移植的(因为您依赖于unique_ptr和其他内容的实现细节)。它还会破坏构造函数的私密性(应该有理由是私有的),因为之后任何人都可以创建make_unique<PrivateClass>! - Don Pedro
我对Herb Sutter在同一主题上的写作中的这条评论感到好奇 https://herbsutter.com/gotw/_102/#comment-5725。本质上,如果这样的`make_unique`被内联(假设它是),那么传递多个参数的函数的等效调用将不再具有异常安全性? - haxpor
2
由于C++17的规定,_unspecified-evaluation-order leak_问题已经得到解决。参考链接 - Константин Ван
以下是与此类似的额外资源:https://isocpp.org/blog/2019/06/quick-q-differences-between-stdmake-unique-and-stdunique-ptr-with-new - KY Lu
显示剩余2条评论

96
区别在于std::make_unique返回类型为std::unique_ptr的对象,而new返回创建对象的指针。对于内存分配失败,它们都会抛出异常。等等,事情并不简单,请继续阅读。

考虑下面这个函数:

void func(ClassA* a, ClassB* b){
     ......
}

当你执行像func(new A(), new B())这样的调用时,编译器可能会选择从左到右评估函数参数,或以任何它希望的顺序进行评估。让我们假设从左到右的评估:当第一个new表达式成功但第二个new表达式抛出异常时会发生什么?
真正的危险在于当你捕获这种异常时;是的,你可能已经捕获了new B()抛出的异常,并恢复了正常执行,但new A()已经成功了,它的内存将被默默地泄漏。没有人来清理它... * 哭泣...
但是使用make_unique,你不会有内存泄漏的问题,因为堆栈展开会发生(并且之前创建的对象的析构函数将运行)。因此,偏好make_unique将限制你对异常安全性的选择。在这种情况下,std::make_unique提供了一个"基本异常安全",由new分配的内存和创建的对象永远不会被孤立无援。即使到时间的尽头... :-)

你应该阅读Herb Sutter GoTW102


6
func(new A(), new B())并不会生成一个unique_ptr,答案是关于当你不使用它时会发生什么的解释。 - Purple Ice
2
清晰地解释了在使用new时由于异常处理不当而出现的问题。第一个答案暗示了异常安全性的理由,但并不清楚。new和make_unique都会抛出异常,但只有make_unique在销毁时处理释放内存。 - Daniel Handojo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接