
为什么 make_shared 比 shared_ptr 构造更高效
因为 make_shared 一次内存分配就能搞定控制块和对象,而直接用 shared_ptr 构造(比如 shared_ptr<t>(new T)</t>)要分配两次:一次给对象,一次给控制块。
控制块里存引用计数、弱引用计数、销毁器等元数据,它必须和对象生命周期解耦——但不意味着非得分开分配。
-
make_shared<int>(42)</int>:底层调用operator new(sizeof(control_block) + sizeof(int)),连续布局 -
shared_ptr<int>(new int(42))</int>:先new int,再单独为控制块分配内存,缓存局部性差,还多一次系统调用开销 - 对小对象(如
int、std::string等内部有 small-string 优化的类型),性能差异明显;对大对象,分配开销占比下降,但构造顺序和异常安全优势仍在
make_shared 不能用于哪些场景
它要求类型必须可公开构造,且不能是 private/protected 构造函数,也不能是 explicit 构造函数(C++17 起放宽了部分限制,但仍有坑)。
常见踩坑点:
立即学习“C++免费学习笔记(深入)”;
- 类只有
explicit单参构造函数时,make_shared<t>(arg)</t>在 C++17 前会编译失败;C++17 后允许,但语义仍是“隐式转换+构造”,需确认是否符合预期 - 类构造函数是
private或protected,且没把std::make_shared声明为友元——此时只能退回到shared_ptr<t>(new T(...))</t>或自定义 factory 函数 - 需要自定义删除器(deleter)或分配器(allocator):
make_shared不接受这些参数,必须用shared_ptr构造函数 - 想用 placement-new 或共享已有内存(比如对象已存在于某 buffer 中):
make_shared总是自己分配,不支持
用 make_shared 时参数转发的陷阱
它用的是完美转发(std::forward),所以实参的值类别(左值/右值)会被保留。这在涉及移动语义或临时对象时容易出问题。
- 传入一个具名的
std::string变量:make_shared<a>(s)</a>→ 会拷贝s,不是移动;想移动得写make_shared<a>(std::move(s))</a> - 传入字面量字符串:
make_shared<:string>("hello")</:string>→ 构造临时std::string,再移动进目标对象,没问题 - 若类
A的构造函数接受const std::string&,而你传了右值,仍会触发拷贝(因 const 引用可绑定右值但不触发移动);这时不如显式用std::move或改接口 - 多个参数时,每个都按原样转发,没有“自动降级”;比如
make_shared<foo>(std::move(a), b, std::forward<c>(c))</c></foo>是合法且推荐的写法
兼容性与实际建议:什么时候别硬上 make_shared
不是所有 shared_ptr 场景都适合 make_shared,尤其当代码要跑在老标准或受限环境里。
- C++11 编译器支持
make_shared,但早期 MSVC 2013 对某些模板推导有 bug;GCC 4.8+、Clang 3.3+ 基本稳 - 如果对象构造可能抛异常,
make_shared的异常安全更好(控制块和对象要么都成,要么都不成);但若你已在用new+shared_ptr手动管理,且逻辑稳定,没必要强行改 - 调试时发现
shared_ptr的use_count()行为异常?检查是否混用了make_shared和裸指针构造——它们的控制块实现不同,但标准保证互操作;不过跨 DLL 边界时,若 ABI 不一致(比如不同 STL 实现),可能崩溃 - 真正该优先选
make_shared的,是新写的、构造简单、无特殊访问控制、不需要自定义 deleter 的代码;其余情况,宁可明确写shared_ptr<t>(new T(...))</t>或封装 factory,也别为了“看起来高级”硬套
控制块内存布局、异常路径下的资源释放、跨模块的 ABI 兼容——这些地方一旦出问题,很难只靠加个 make_shared 解决。











