【问题标题】:Advantages of using std::make_unique over new operator [duplicate]使用 std::make_unique 优于 new 运算符的优势 [重复]
【发布时间】:2016-09-27 15:00:04
【问题描述】:

new 运算符相比,使用std::make_unique 初始化std::unique_ptr 有哪些优势?

换句话说,为什么是

std::unique_ptr<SomeObject> a = std::make_unique(SomeObject(...))

比做更好

std::unique_ptr<SomeObject> a = new SomeObject(...)

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

【问题讨论】:

  • 其实std::make_unique(SomeObject(...))导致调用构造函数和复制/移动构造函数。当然,更好的选择是直接使用底层模板类型的构造函数参数调用std::make_unique

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


【解决方案1】:

优势

  • make_unique 教导用户“永远不要说 new/deletenew[]/delete[]" 没有免责声明。

  • make_uniquemake_shared 有两个优点(不包括第三个优点,提高效率)。首先unique_ptr&lt;LongTypeName&gt; up(new LongTypeName(args))必须提到LongTypeName两次,而auto up = make_unique&lt;LongTypeName&gt;(args)提到一次。

  • make_unique 防止未指定的评估顺序 由foo(unique_ptr&lt;X&gt;(new X), unique_ptr&lt;Y&gt;(new Y)) 等表达式触发的泄漏。 (遵循“永远不要说new”的建议比 “永远不要说new,除非你立即把它交给一个名为unique_ptr”。)

  • make_unique 为异常安全而精心实现,建议直接调用 unique_ptr 构造函数。

什么时候不使用make_unique

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

来源

  1. Proposal of std::make_unique
  2. Herb Sutter's GotW #89 Solution: Smart Pointers

【讨论】:

  • 感谢您的回答。请您解释一下您关于何时不使用它的理由?
  • 还有一种不使用 make_unique 的情况:私有或受保护的构造函数!请不要听任何关于 SO 的建议或网络上建议制作 make_unique 或 unique_ptr 或任何你班上的朋友。这是不可移植的(因为你依赖于 unique_ptr 和其他东西的实现细节)。它打破了构造函数的私有性(应该有理由成为私有的),因为之后任何人都可以 make_unique!
  • 我对 Herb Sutter 关于同一主题 herbsutter.com/gotw/_102/#comment-5725 的文章的评论感到好奇。本质上,如果这样的make_unique 是内联的(假设它是内联的),那么对接受多个参数的函数的等效调用将不再具有异常安全性?
  • @haxpor 异常安全基于对象的生命周期,与内联等优化无关。您所谓的“等效调用函数”并不等效,因为它没有考虑make_unique&lt;...&gt; 函数体的范围。
  • 关于第二点。不幸的是 make_unique 似乎没有对初始化列表的强大支持。所以unique_ptr&lt;TypeWithDeepMap&gt;(new TypeWithDeepMap({})) 变成make_unque&lt;TypeWithDeepMap&gt;(std::map&lt;std::string, std::map&lt;std::string, std::string&gt;&gt;({}))
【解决方案2】:

区别在于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 会限制您使用exception safety。在这种情况下,std::make_unique 提供了一个"Basic Exception Safety",它分配的内存和由new 创建的对象无论如何都不会成为孤立对象。甚至到时间的尽头... :-)

你应该阅读Herb Sutter GoTW102

【讨论】:

  • @Nik-Lz func(new A(), new B()) 没有unique_ptr,答案是当你不使用它时会发生什么......
猜你喜欢
  • 1970-01-01
  • 2013-09-17
  • 2017-01-06
  • 1970-01-01
  • 2012-08-14
  • 1970-01-01
  • 2010-11-15
  • 1970-01-01
  • 2014-03-04
相关资源
最近更新 更多