EMCPP条款21:优先选用std::make_unique和std::make_shared,而非直接使用new
优先选用的场景
优先选用 make
系列函数的第一个原因,避免重复撰写型别
1 | auto upw1(std::make_unique<Widget>()); // with make func |
优先使用 make
系列函数的第二个原因与异常安全有关。假设我们有一个函数依据某种优先级来处理一个 Widget
对象和一个函数用来计算相对优先级::
1 | void processWidget(std::shared_ptr<Widget> spw, int priority); |
我们在 processWidget
的调用中用到该函数,并且在这次调用中,processWidget
使用了 new
运算符而非 std::make_shared
:
1 | processWidget(std::shared_ptr<Widget>(new Widget), computePriority()); |
代码在什么地方会发生资源泄漏呢?答案与编译器从源代码到目标代码的翻译有关。在运行期,传递给函数的实参必须在函数调用被发起之前完成评估求值。因此,在 processWidget
的调用过程中,下列事件必须在 processWidget
开始执行前发生:
- 表达式
new Widget
必须先完成评估求值,即,一个Widget
对象必须先在堆上创建。 - 由
new
产生的裸指针的托管对象std::shared_ptr<Widget>
的构造函数必须执行。 computePriority
必须运行。
编译器不必按上述顺序来生成代码。编译器可能会放出这样的代码,以按如下时序执行操作:
- 实施
new Widget
。 - 执行
computePriority
。 - 运行
std::shared_ptr
构造函数。
如果生成了这样的代码,并且在运行期computePriority
产生了异常,那么由第一步动态分配的Widget
会被泄漏,因为它将永远不会被存储到在第三步才接管的std::shared_ptr
中去。
使用 std::make_shared
可以避免该问题。调用代码如下:
1 | processWidget(std::make_shared<Widget>(), computePriority()); |
在运行期,std::make_shared
和 computePriority
中肯定有一个会首先披调用。
如果我们把 std::shared_ptr
和 std::make_shared
分别替换成 std::unique_ptr
和 std::make_unique
, 则推理过程完全相同。
std::make_shared
的另 一个特色(与直接使用 new
表达式相比),是性能的提升。使用 std::make_shared
会让编译器有机会利用更简洁的数据结构产生更小更快的代码。
不能使用的场景
本条款仍然主张的是优先选用 make
系列函数,而非排他性地使用之。原因在于,还是有一些情景之下,不能或者不应使用 make
系列函数。例如,所有的 make
系列函数都不允许使用自定义析构器
1 | auto widgetDeleter = [](Widget* pw) { … }; |
make
系列面数的第二个限制源于其实现的一个语法细节。 make
系列面数会向对象的构造函数完美转发其形参,但当它们到底是在使用圆括号的时候这样做,还是在使用大括号时这样做呢?对于某些型别而言,这个问题的答案会根据使用括号种类的不同而有很大不同。
有些类会定义自身版本的 opeator new
和 operator delete
, 这些函数的存在意味着全局版本的内存分配和释放函数不适用于这种对象。因此,使用 make
系列函数去为带有自定义版本的 operate new
和 operator delete
的类创建对象,通常并不是个好主意。
下一个限制源于控制块和指涉对象的析构有关,参见原文
使用了自定义析构器的话,法使用 std::make_shared
。
要点速记
- 相比于直接使用
new
表达式,make
系列函数消除了重复代码、改进了异常安全性,并且对于std::make_shared
和std::allcoated_shared
而言,生成的目标代码会尺寸更小、速度更快。 - 不适于使用
make
系列函数的场景包括需要定制删除器,以及期望直接传递大括号初始化物。 - 对于
std::shared_ptr
, 不建议使用make
系列函数的额外场景包括- 自定义内存管理的类
- 内存紧张的系统、非常大的对象、以及存在比指涉到相同对象的
std::shared_ptr
生存期更久的std::weak_ptr
。