Prefer std::make_unique
and std::make_shared
to direct use of new
一般情况下, 更倾向于使用make系列函数来创建智能指针,而不是直接使用new。
首先, 它避免了代码重复。使用new
的版本重复了类型,但是make
函数的版本没有。
1 | auto upw1(std::make_unique<Widget>()); //使用make函数 |
其次, make 系列函数有助于实现更好的异常安全代码。考虑以下代码:
1 | void processWidget(std::shared_ptr<Widget> spw, int priority); |
在运行时,必须执行以下操作,processWidget
才开始执行:
- 一个
Widget
对象必须在堆上被创建 std::shared_ptr<Widget>
构造函数执行- 运行
computePriority()
以上三个操作可以按任意顺序执行。但如果正好按以顺序执行,computePriority()
抛出异常, 第一步动态分配的Widget
就会泄漏。
使用std::make_shared
可以防止这种问题:
1 | processWidget(std::make_shared<Widget>(), computePriority()); //没有潜在的资源泄漏 |
第三,使用std::make_shared
能提升效率。
直接使用new
需要为Widget
进行两次内存分配,一次为Widget
对象,一次为控制块。
1 | std::shared_ptr<Widget> spw(new Widget); |
使用std::make_shared
只分配一块内存,同时容纳了Widget
对象和控制块。
1 | auto spw = std::make_shared<Widget>(); |
但是有些情况下不适用std::make_shared
。例如,make
函数都不允许指定自定义删除器。如果想为智能指针指定一个自定义删除器, 只能直接使用 new:
1 | auto widgetDeleter = [](Widget* pw) { … }; |
第二,make
函数中,完美转发使用小括号,而不是花括号。如果想用花括号初始化指向的对象,就必须使用new
。
1 | // a vector of 10 elements, each equal to 20 |
对于std::shared_ptr
和它的make
函数,还有2个问题。
第一是一些重载了operator new
和operator delete
的类。他们不太适用于std::shared_ptr
对自定义分配和释放的支持。
第二是分配的对象非常大并且有 std::weak_ptr 指向它。通过std::shared_ptr
的make
函数分配的内存(对象+控制快),直到最后一个std::shared_ptr
和最后一个指向它的std::weak_ptr
都下降到 count=0 时,才会释放控制快,从而释放内存。这不适用于系统内存很吃紧的情况。
如果处于不合适使用std::make_shared
的情况下,最好的方法是确保在直接使用new
时,在一个不做其他事情的语句中,立即将结果传递到智能指针构造函数。防止在使用new
和调用管理new
出来对象的智能指针的构造函数之间发生异常。
1 | processWidget(std::shared_ptr<Widget>(new Widget, cusDel), computePriority()); |
重点
相比于直接使用new, std::make_unique和std::make_shared可以消除代码重复, 提高异常安全性且生成的代码更小更快。
不适合使用std::make_X系列函数的情况包括需要指定自定义删除器和希望用花括号初始化。
对于 std::shared_ptr, 其他不建议使用make 系列函数的情况还包括 (1) 具有自定义内存管理的类 (2) 有内存担忧的系统(3) 非常大的对象、(4)比相应的 std::shared_ptr 生命周期更长的的 std::weak_ptr。