Item21:优先使用make_unique和make_shared而非直接使用new
std::make_shared是 C++11 标准的,而std::make_unique直到 C++14 才加入;- 如果用 C++11,可手写简易版
make_unique(仅支持基础场景,不支持数组 / 自定义析构),但注意不要放到std命名空间,避免和 C++14 标准库冲突; - 三个 make 函数:
make_unique、make_shared、allocate_shared(allocate_shared和make_shared行为一致,只是支持自定义内存分配器),它们的共性是 “接收任意参数→完美转发给对象构造函数→动态分配对象→返回对应智能指针”。
优先用 make 函数
避免类型重复
直接用new创建智能指针时,类型会写两次,而 make 函数只需写一次,符合 “避免重复代码” 的软件工程原则:
1 | // 重复写Widget:不够简洁 |
避免内存泄漏
这是最关键的优势。直接用new时,编译器可能调整代码执行顺序,导致动态分配的对象因异常泄漏;而 make 函数能保证对象被智能指针及时接管。
反例(有泄漏风险):
1 | // 危险:如果computePriority()在new Widget后、shared_ptr构造前抛异常,Widget会泄漏 |
执行顺序可能是:new Widget → computePriority()(抛异常) → shared_ptr构造(永远执行不到),此时new出的 Widget 无智能指针管理,直接泄漏。
正例(无泄漏风险):
1 | // 安全:make_shared先创建对象并封装到shared_ptr,再调用computePriority() |
- 若
make_shared先执行:对象已被shared_ptr接管,即使computePriority()抛异常,shared_ptr析构会释放对象; - 若
computePriority()先执行并抛异常:make_shared根本不会调用,无对象分配,自然无泄漏。
这个逻辑对make_unique/unique_ptr同样适用。
效率更高(仅针对make_shared/allocate_shared)
直接用new创建shared_ptr会触发两次内存分配:一次给对象本身,一次给shared_ptr的控制块(存储引用计数等信息);
而make_shared只需一次内存分配:将对象和控制块放在同一块内存中,减少了内存分配次数,代码更小、运行更快,还能减少内存占用。
不适合使用 make 函数的场景
需要指定自定义删除器
make 函数不支持传入自定义删除器,但unique_ptr/shared_ptr的构造函数支持,此时只能直接new:
1 | auto widgetDeleter = [](Widget* pw) { /* 自定义析构逻辑 */ }; |
需要用花括号初始化对象
make 函数的参数是通过小括号完美转发的,无法直接转发花括号初始化(std::initializer_list)。若要初始化vector<int>{10,20}这类对象,需先创建initializer_list再传递:
1 | // 不推荐:make_unique用小括号,会创建10个值为20的元素,而非{10,20} |
针对shared_ptr的额外场景(unique_ptr无此限制)
- 类重载了
operator new/operator delete:这类自定义内存管理通常只适配 “对象大小” 的内存,而make_shared需要分配 “对象 + 控制块” 的内存,不兼容; - 超大对象 +
weak_ptr存活更久:make_shared的对象和控制块同内存,只有最后一个shared_ptr和最后一个weak_ptr都销毁,内存才释放;若超大对象的shared_ptr先销毁,weak_ptr还在,对象内存会一直占用(直接new则shared_ptr销毁后对象内存就释放,仅留控制块)。
无法用 make 函数时的异常安全写法
如果必须用new(比如自定义删除器),要避免异常泄漏,需将 “new+ 智能指针构造” 单独放一行,再传递给函数;若想优化性能,可用std::move将左值转为右值(避免shared_ptr拷贝时的原子引用计数递增):
1 | // 第一步:单独语句创建shared_ptr,确保对象被接管 |
总结
- 优先用
make_unique/make_shared的原因:消除代码重复、提升异常安全性,make_shared还能提升内存 / 执行效率; - 不能用 make 函数的场景:需自定义删除器、需花括号初始化,
shared_ptr还需额外考虑自定义内存管理 / 超大对象 +weak_ptr存活更久的情况; - 无法用 make 函数时,需将
new和智能指针构造单独放一行,保证异常安全,必要时用std::move优化性能。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 肖恩的博客!
评论
