std::make_shared 更安全因一次分配控制块与对象,避免异常时资源泄漏;但不可用于私有构造、需自定义删除器或特殊内存布局场景,且参数转发易致悬空引用。

std::make_shared 为什么比 new + shared_ptr 构造更安全
因为 std::make_shared 在一次内存分配中同时构造控制块和对象,避免了两次分配(new 对象 + new 控制块)可能引发的异常安全问题。如果 new 对象成功但控制块分配失败,手动构造的 shared_ptr 会导致资源泄漏。
- 常见错误现象:
shared_ptr<t>(new T(args...))</t>在构造参数里抛异常时,T已分配但无法被管理,泄漏 - 使用场景:只要不需要自定义删除器、不需要从裸指针转换、不涉及继承关系中的多态删除,就该优先用
std::make_shared - 性能影响:减少一次内存分配+释放,尤其在高频创建时可观;某些实现还会做小对象优化(把控制块和对象紧邻布局)
哪些情况不能用 std::make_shared
std::make_shared 要求类型必须可公开访问构造函数,且不能绕过构造逻辑——它本质上是调用 T(args...)。一旦你依赖私有构造、委托构造、或需要延迟初始化,它就失效。
- 常见错误现象:编译报错
error: 'T::T(...)' is private或no matching function for call to 'make_shared' - 典型场景:
- 类只有私有/受保护构造函数(如单例、工厂封装)
- 需要传入自定义删除器(比如用
fclose管理FILE*) - 对象需先分配内存再 placement-new(如对齐要求特殊、或与内存池集成)
- 替代写法:老老实实用
shared_ptr<t>(new T(...), [](T* p){ /* custom cleanup */ })</t>,但务必确保异常安全
std::make_shared 的参数转发陷阱
它完美转发参数给 T 的构造函数,但容易忽略引用折叠和临时对象生命周期——尤其是传入非 const lvalue 引用参数时。
- 常见错误现象:传入局部变量引用,对象构造后引用悬空,但
make_shared内部已将该引用绑定到即将被移动/销毁的临时对象 - 关键差异:
make_shared的参数在函数体内被转发,不是在调用者作用域延长生命周期;而直接new T(x)中的x是调用者负责生命周期 - 安全做法:
- 只传值或 const lvalue 引用(
const std::string&安全) - 避免传非常量左值引用(
std::string&危险) - 不确定时,显式拷贝:用
make_shared<t>(s.c_str(), s.size())</t>而非make_shared<t>(s)</t>(若T构造函数接受std::string&&可能窃取)
- 只传值或 const lvalue 引用(
和 std::shared_ptr 构造函数的兼容性细节
std::make_shared 返回的是 shared_ptr<t></t>,类型完全一致,但内部布局不同——控制块和对象共分配,导致 shared_ptr 的别名构造(aliasing constructor)行为不变,但地址比较需注意。
立即学习“C++免费学习笔记(深入)”;
- 常见错误现象:误以为
make_shared<t>()->get() == ptr.get()</t>在所有情况下都成立(其实仅当没用别名构造时才成立) - 兼容性影响:所有标准操作(
reset、swap、use_count)完全一致;唯一区别是内存布局,不影响 ABI 或接口 - 特别提醒:不要对
make_shared创建的对象取地址再转成其他智能指针(如unique_ptr),控制块不会被识别,析构会崩溃
事情说清了就结束。最常被忽略的是参数转发时的临时对象生命周期,以及误以为 make_shared 能绕过访问控制——它不能。











