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

Why use std::make_unique in C++17?

据我了解,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

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

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

您省去了 MyClassnew 和大括号。与 ptr.

相比,make 只多花一个字符

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

另外不要忘记一致性。你绝对应该使用 make_shared 所以使用 make_unique 是自然的并且符合模式。然后将 std::make_unique<MyClass>(param) 更改为 std::make_shared<MyClass>(param)(或相反)是微不足道的,其中语法 A 需要更多的重写。

每次使用 new 都必须格外仔细地审核其生命周期的正确性;它会被删除吗?只有一次?

每次使用 make_unique 都不是为了那些额外的特征;只要拥有对象的生命周期为 "correct",它就会递归地使唯一指针具有 "correct".

现在,unique_ptr<Foo>(new Foo()) 确实在所有方面都是相同的1make_unique<Foo>();它只需要一个更简单的 "grep your source code for all uses of new to audit them".


1一般情况下其实是骗人的。完美转发不完美,{},默认init,数组都是例外

make_unique 区分 TT[]T[N]unique_ptr(new ...) 则不然。

您可以通过将 new[]ed 的指针传递给 unique_ptr<T> 或将 newed 的指针传递给 [=18= 来轻松获得未定义的行为].

Since then, C++17 has clarified the evaluation order, making Syntax A safe too

这真的不够好。依靠最近引入的技术条款作为安全保证并不是一个非常稳健的做法:

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

一般来说,最好让您的代码appropriate/robust/clearly有效而无需诉诸语言分层,查找标准中次要或模糊的技术条款。

(这与我 关于元组销毁顺序的论点基本相同。)

考虑一下 void function(std::unique_ptr(new A()), std::unique_ptr(new B())) { ... }

假设 new A() 成功了,但是 new B() 抛出异常:你捕获它以恢复程序的正常执行。不幸的是,C++ 标准不要求对象 A 被销毁并释放其内存:内存悄无声息地泄漏并且没有办法清理它。通过将 A 和 B 包装到 std::make_uniques 中,您可以确定不会发生泄漏:

void function(std::make_unique(), std::make_unique()) { ... } 这里的重点是 std::make_unique 和 std::make_unique 现在是临时对象,临时对象的清理在 C++ 标准中正确指定:它们的析构函数将被触发并释放内存。所以如果可以的话,总是喜欢使用 std::make_unique 和 std::make_shared.

分配对象