make_shared比new+shared_ptr更安全,因其将对象与控制块一次性分配,避免构造异常导致的内存泄漏;但不支持自定义删除器、裸指针接管或私有构造函数访问。

make_shared 为什么比 new + shared_ptr 构造更安全
直接用 new 配合 shared_ptr 构造函数(如 shared_ptr<t>(new T(args))</t>)容易引发异常安全问题:如果 T 的构造函数抛异常,new T(args) 分配的内存会泄漏,因为 shared_ptr 构造器还没来得及接管它。
make_shared 把对象构造和控制块(reference count、weak count 等)分配合并为一次堆分配,原子完成。即使构造失败,也不会泄漏内存。
- 推荐始终优先使用
make_shared,除非你需要自定义删除器或需要将裸指针传给第三方 API -
make_shared不支持传递自定义删除器 —— 这时只能退回到shared_ptr<t>(new T, my_deleter)</t> - 某些调试器或内存分析工具对
make_shared的布局不友好(控制块和对象紧邻),但这是极少数场景
new + shared_ptr 构造的典型错误写法
下面这段代码看似无害,实则危险:
shared_ptr<MyClass> ptr(new MyClass(heavy_init(), other_call()));
参数求值顺序未定义:heavy_init() 和 other_call() 可能任意顺序执行;若其中一个抛异常,另一个已成功执行的副作用无法回滚,且 new MyClass(...) 若已执行但后续抛异常,内存就泄漏了。
立即学习“C++免费学习笔记(深入)”;
- 正确做法是拆成两步,显式控制生命周期:
auto raw = new MyClass(...); shared_ptr<MyClass> ptr(raw);—— 但这样仍不解决构造异常问题 - 更稳妥的是先确保所有参数计算完成,再调用
make_shared:auto a = heavy_init(); auto b = other_call(); auto ptr = make_shared<MyClass>(a, b); - 永远不要在
shared_ptr构造函数参数列表里做有副作用或可能抛异常的操作
make_shared 的内存布局与性能差异
make_shared 对大多数类型更快:一次分配代替两次(控制块 + 对象),减少内存碎片和分配开销。但它把对象和控制块放在同一块内存中,导致:
- 即使最后一个
shared_ptr被销毁,只要还有weak_ptr存活,整个内存块(含对象)都不能释放 —— 对象析构被延迟到所有weak_ptr也销毁后 - 如果对象很大、但
weak_ptr寿命很长,会造成不必要的内存驻留 - 对于 trivially destructible 类型(如 POD),这点影响不大;但对持有大缓冲区或需及时释放资源的类,要留意
什么时候必须用 new 而不能用 make_shared
三个明确场景:
- 需要自定义删除器:
shared_ptr<FILE>(fopen(...), [](FILE* f){ fclose(f); })——make_shared没有接口传删除器 - 要将已有裸指针交给
shared_ptr管理(比如 C API 返回的指针) - 类的构造函数是私有的,且
make_shared无法访问(friend 声明只对模板实例化生效,但标准库实现不一定暴露足够 friend 关系);此时可改用shared_ptr的别名构造或工厂函数封装
这些情况不常见,但一旦遇到,硬套 make_shared 会编译失败或行为未定义。


















