std::make_unique和使用new创建std::unique_ptr之间的区别

195

std::make_unique是否像std::make_shared一样有任何效率优势?

与手动构造std::unique_ptr相比:

std::make_unique<int>(1);         // vs
std::unique_ptr<int>(new int(1));

1
make_shared 相较于手写长代码是否更加高效呢? - Ed Heal
11
可能会,因为make_shared可以在单个分配中同时分配对象和控制块的空间。代价是如果您经常使用weak_ptr,那么对象无法单独释放,必须与控制块一起释放,这可能导致使用更多的内存。 - bames53
也许这是一个不错的起点 https://dev59.com/vmox5IYBdhLWcg3wSCfk - Ed Heal
请查看此链接以获取详细说明:https://herbsutter.com/gotw/_102/ - Krishna Kanth Yenumula
4个回答

196
< p > make_unique 的动机主要有两个方面:
  • make_unique is safe for creating temporaries, whereas with explicit use of new you have to remember the rule about not using unnamed temporaries.

    foo(make_unique<T>(), make_unique<U>()); // exception safe
    
    foo(unique_ptr<T>(new T()), unique_ptr<U>(new U())); // unsafe*
    
  • The addition of make_unique finally means we can tell people to 'never' use new rather than the previous rule to "'never' use new except when you make a unique_ptr".

还有第三个原因:

  • make_unique 不需要冗余类型使用。 unique_ptr<T>(new T()) -> make_unique<T>()

这些原因都不涉及像使用 make_shared 一样改善运行时效率(因为避免了第二次分配,但可能导致峰值内存使用量更高)。

* 预计 C++17 将包含规则更改,这意味着这种情况将不再是不安全的。请参阅 C++ 委员会文件P0400R0P0145R3


更合理的说法是,std::unique_ptrstd::shared_ptr是我们可以告诉人们“永远不要使用new”的原因。 - Timothy Shields
2
@TimothyShields 是的,这就是我的意思。只是在C++11中,我们有了make_shared,所以make_unique是之前缺失的最后一块拼图。 - bames53
2
你能简要提及或链接一下不使用匿名临时变量的原因吗? - Dan Nissenbaum
28
实际上,我从 https://dev59.com/AmIk5IYBdhLWcg3wN71j#19472607 这个答案中得到了答案...考虑以下函数调用 f: f(unique_ptr<T>(new T), function_that_can_throw()); - 引用答案:编译器可以按照顺序调用:new Tfunction_that_can_throw()unique_ptr<T>(...)。显然,如果 function_that_can_throw 真的抛出异常,那么会有内存泄漏。make_unique 可以防止这种情况发生。 因此,我的问题已得到解答。 - Dan Nissenbaum
3
我曾经使用std::unique_ptr<T>(new T())的原因之一是因为T的构造函数是私有的。即使调用std::make_unique的公共工厂方法属于类T,它也无法编译,因为std::make_unique的底层方法之一无法访问私有构造函数。我不想将该方法设置为友元,因为我不想依赖于std::make_unique的实现。所以唯一的解决方案就是在我的类T的工厂方法中调用new,然后将其包装在std::unique_ptr<T>中。 - Patrick
显示剩余4条评论

24

std::make_uniquestd::make_shared 的存在有两个原因:

  1. 这样你就不必显式列出模板类型参数。
  2. 相较于使用 std::unique_ptrstd::shared_ptr 构造函数,它们具有额外的异常安全性。(参见此处的注释部分。)

这并不是关于运行时效率的。其中有关控制块和 T 一次性分配的内容,但我认为这更多是一个奖励而非这些函数存在的动机。


1
它们也存在于异常安全方面。 - David G
@0x499602D2,不错的补充。这个页面讲述了相关内容。 - Timothy Shields
1
对于未来的读者而言,C++17不再允许交错函数参数,因此异常安全性的参数不再适用。两个并行的std::make_shared的内存分配将确保在另一个内存分配发生之前至少有一个被包装在智能指针中,因此不会出现泄漏。 - MathBunny

17

如果无法在当前作用域外访问类 A 的构造函数,则需要直接使用 std::unique_ptr(new A())std::shared_ptr(new A()),而不能使用 std::make_*()


有没有计划修复C++,让make_unique和make_shared可以访问私有构造函数? - nyanpasu64
3
@nyanpasu64,make_unique不需要修复。您可以将其设置为类的友元,并使用私有构造函数。 - Volodymyr Lashko

5

考虑函数调用

void function(std::unique_ptr<A>(new A()), std::unique_ptr<B>(new B())) { ... }

假设new A()成功,但new B()抛出异常:您捕获它以恢复程序的正常执行。不幸的是,C++标准并不要求销毁对象A并释放其内存:内存会默默泄漏,并且没有办法清理它。通过将AB封装在std::make_unique()调用中,您可以确保不会发生内存泄漏:
void function(std::make_unique<A>(), std::make_unique<B>()) { ... }

这里的重点是 std::make_unique<A>()std::make_unique<B>() 返回临时对象,C++标准正确地指定了临时对象的清理方式:它们的析构函数将被触发并释放内存。因此,如果可以的话,始终优先使用std::make_unique()std::make_shared()来分配对象。

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