std::make_shared
是C++11一部分,std::make_unique
为C++14才引入,std::allocate_shared
它的行为和std::make_shared
一样,只不过它的第一个实参是个用以动态分配内存的分配器对象;
是否对make
系列函数来创建一个智能指针坐最平凡的对比,也能揭示优先选用make
系列函数的第一个原因,考虑如下代码:
auto upw1(std::make_unique<Widget>()); // 使用make系列函数
std::unique_ptr<Widget> upw2(new Widget); // 不使用make系列函数
auto spw1(std::make_shared<Widget>()); // 使用make系列函数
std::shared_ptr<Widget> spw2(new Widget); // 不使用make系列函数
使用了new
的版本將被创建对象(Widget)的类型重复写了两遍,而make
系列没有,这违反了软件工程避免代码冗余的原则。源代码中的重复会增加编译遍数,导致臃肿的目标代码。
优先使用make
系列函数另一个原因是与异常安全相关。考虑有一个函数一句某种优先级来处理一个Widget
对象:
void processWidget(std:shared_ptr<Widget> spw, int priority);
int computerPriority(); //计算优先级
processWdiget(std::shared_ptr<Widget>(new Widget), computerPriority()); // 潜在的资源泄漏
如注释,这段代码会因为使用了new
运算符而导致内存泄漏,原因是因为编译器从源代码到目标代码的翻译有关。如下事件必须在processWidget
开始执行前发生:
- 一个
Widget
对象必须线在堆上建立 - 由
new
产生的裸指针的托管对象std::shared_ptr<Widget>
的构造函数必须执行 - computerPriority`必须运行
编译器不一定按照上述顺序来执行代码。1必须在2之前执行,但是3可以在1,2之前/之后/中间掉用
如果执行顺序为1->3->2,并且3在出现了异常,那么1就会产生内存泄漏,因为它永远不会被存储到std::shared_ptr
中;使用std::make_shared
可以避免该问题:
processWdiget(std::make_shared<Widget>(), computerPriority());
使用std::make_shared
会让编译器有机会利用更简洁的数据结构产生更小更快的代码,考虑如下使用new
的代码
std::shared_ptr<Widget> spw(new Widget);
每个std::shared_ptr
会涉及一个控制块,控制块内存是std::shared_ptr
的构造函数进行分配的,还有一次Widget
的内存分配,上述代产生两次内存分配;
如果使用std::make_shared<Widget>()
代替的话:
auto spw = std::make_shared<Widget>()
因为std::make_shared
会分配单块内存,既保存Widget
对象又保存与其相关的控制块
有以下情景不应该使make
系列函数
- 所有
make
系列函数都不允许使用自定义析构器 - 无法使用大括号初始化
对于std::shared_ptr
,不建议使用make
系列函数的场景包括:
- 自定义管理内存的类
- 内存紧张的系统,非常大的对象,以及在比涉及到相同对象的
std::shared_ptr
生存期更久的std::weak_ptr