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

As far as I understand, C++14 introduced std::make_unique because, as a result of the parameter evaluation order not being specified, this was unsafe:

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

(Explanation: if the evaluation first allocates the memory for the raw pointer, then calls g() and an exception is thrown before the std::unique_ptr construction, then the memory is leaked.)

Calling std::make_unique was a way to constrain the call order, thus making things safe:

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

Since then, C++17 has clarified the evaluation order, making Syntax A safe too, so here's my question: is there still a reason to use ABC0 over std::unique_ptr's constructor in C++17? Can you give some examples?

As of now, the only reason I can imagine is that it allows to type MyClass only once (assuming you don't need to rely on polymorphism with std::unique_ptr<Base>(new Derived(param))). However, that seems like a pretty weak reason, especially when std::make_unique doesn't allow to specify a deleter while std::unique_ptr's constructor does.

And just to be clear, I'm not advocating in favor of removing std::make_unique from the Standard Library (keeping it makes sense at least for backward compatibility), but rather wondering if there are still situations in which it is strongly preferred to std::unique_ptr

21820 次浏览

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

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

你可以保存 MyClassnew和大括号。它在 制造中只比 Ptr多花费一个字符。

你说得对,主要原因被移除了。还有 don't use new的指导方针,这是较少的输入原因(不必重复的类型或使用字 new)。不可否认,这些都不是强有力的论据,但我真的希望在我的代码中看不到 new

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

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

每次使用 make_unique并不是为了这些额外的特性; 只要拥有的对象具有“正确的”生存期,它就递归地使唯一指针具有“正确的”生存期。

现在,unique_ptr<Foo>(new Foo())在所有方面与 make_unique<Foo>()完全相同; 它只需要一个更简单的“ grep 您的源代码,以便对 new的所有用途进行审计”。


1 实际上在一般情况下是一个谎言。完美转发不是完美的,{},默认 init,数组都是例外。

make_unique区分 TT[]T[N],而 unique_ptr(new ...)不能。

通过传递一个指向 unique_ptr<T>new[]ed 指针,或者传递一个指向 unique_ptr<T[]>newed 指针,您可以很容易地获得未定义的行为。

此后,C + + 17明确了评估顺序,使得语法 A 更加安全

这真的不够好。依靠最近引入的技术条款作为安全保障并不是一种非常有力的做法:

  • 可能会有人用 C + + 14编译这段代码。
  • 您可能会鼓励在其他地方使用原始的 new,例如通过复制粘贴您的示例。
  • 正如 SM 建议的那样,由于存在代码复制,一种类型可能会发生改变,而另一种类型不会发生改变。
  • 某种类型的自动 IDE 重构可能会将 new移到其他地方(好吧,我承认,这种可能性不大)。

一般来说,使用适当的/健壮的/清晰有效的 没有来进行语言分层、查找标准中的次要或晦涩的技术条款是一个好主意。

(this is essentially the same argument I made here about the order of tuple destruction.)

考虑一下 Void 函数(std: : special _ ptr (new A ()) ,std: : special _ ptr (new B ())){ ... }

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

Void 函数(std: : make _ only () ,std: : make _ only ()){ ... } The point here is that std::make_unique and std::make_unique are now temporary objects, and cleanup of temporary objects is correctly specified in the C++ standard: their destructors will be triggered and the memory freed. So if you can, always prefer to allocate objects using std::make_unique and std::make_shared.