make_shared 更安全高效:一次性分配控制块与对象内存,避免双重分配及异常泄漏;但不支持自定义删除器或别名构造,此时必须用 shared_ptr 原生构造。

为什么 make_shared 比 shared_ptr 构造更安全且高效
因为 make_shared 一次性分配控制块和对象内存,避免两次堆分配;同时构造失败时自动回滚,不会泄露资源。而直接用 shared_ptr 构造(如 shared_ptr)先 new 对象,再构造控制块,中间若抛异常,new 出的对象就泄漏了。
常见错误现象:shared_ptr 中若 T 构造抛异常,new T 的内存无法被 shared_ptr 管理,直接泄漏。
-
make_shared是原子操作:要么全部成功(对象+控制块都就位),要么全不发生 - 对 POD 或小对象,内存局部性更好,缓存友好
- 不支持自定义删除器或别名构造(需要时仍得用
shared_ptr原生构造)
make_shared 不支持自定义删除器?那什么时候必须用 shared_ptr 原生构造
当需要非默认资源释放逻辑时,比如关闭文件描述符、解除 C 风格句柄引用、或管理栈上/内存池中的对象,make_shared 无法传入删除器。
典型场景:
立即学习“C++免费学习笔记(深入)”;
- 包装
FILE*:需用fclose而非delete - 管理
pthread_mutex_t:需调用pthread_mutex_destroy - 指向子对象的
shared_ptr(别名构造):如shared_ptr(ptr, [](int*){})
此时必须写成:shared_ptr,但要注意异常安全——务必确保 new T 和 lambda 构造都不抛异常,否则仍有泄漏风险。
使用 make_shared 时参数转发的陷阱
make_shared 完美转发参数给 T 的构造函数,但若 T 构造函数接受非 const 引用(如 T(int&)),而你传入字面量(如 make_shared),会编译失败——因为不能绑定右值到非常量左值引用。
容易踩的坑:
- 传入临时对象时,注意其生命周期是否被延长(
make_shared内部完美转发,但不延长实参生命) - 若
T有初始化列表构造函数,make_shared可能被误解析为({a,b}) std::initializer_list,而非预期的成员初始化 - 参数含移动语义时(如
make_shared),确保>(std::move(v)) v后续不再访问
性能差异在什么规模下才明显?要不要为每处都换
单次分配节省约一个指针大小(控制块)+ 分配器开销,通常几十纳秒。对高频创建/销毁(如网络请求每毫秒新建几十个消息对象)有意义;对生命周期长、创建稀疏的对象(如全局配置单例),收益可忽略。
但异常安全是无条件优势:只要 T 构造可能抛异常,make_shared 就比裸 new + shared_ptr 更可靠。
真正容易被忽略的是:即使你没显式 throw,T 的成员变量构造、allocator 分配、甚至 std::string 内部 malloc 都可能抛 std::bad_alloc —— 这类隐式异常让裸 new 构造 shared_ptr 实际上很脆弱。









