std::make_shared 更高效因仅需一次内存分配,而 shared_ptr(new t) 至少两次(对象+控制块),减少 cache miss;但不支持私有构造、自定义删除器、裸指针包装、数组类型等场景。

为什么 std::make_shared 比直接构造 std::shared_ptr 更高效
因为 std::make_shared 只做一次内存分配,而 std::shared_ptr(new T(args...)) 至少要分配两次:一次给对象 T,一次给控制块(control block)。控制块里存引用计数、弱引用计数等元数据。两次分配不仅慢,还增加 cache miss 概率,尤其在频繁创建小对象时差异明显。
实操上,只要不是需要自定义删除器或需要从裸指针迁移的场景,都该优先用 std::make_shared。
std::make_shared 不能替代所有 std::shared_ptr 构造场景
以下情况无法用 std::make_shared:
-
T的构造函数是私有的,且std::make_shared无法访问(它不参与类的友元声明) - 需要传入自定义删除器,比如
std::shared_ptr<file>(fopen(...), fclose)</file> - 已有裸指针,必须包装(例如 C API 返回的指针),此时只能用
std::shared_ptr<t>(raw_ptr, deleter)</t> -
T是数组类型(std::shared_ptr<int></int>),std::make_shared不支持变长数组构造
使用 std::make_shared 时参数转发的细节要注意
std::make_shared 内部用完美转发调用 T 的构造函数,所以传参行为和直接写 new T(...) 一致,但容易忽略隐式转换或临时对象生命周期问题:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 避免传入临时对象的引用(如
std::string&&参数被转发后,若T保存了该引用,会悬垂) - 若
T构造函数有std::initializer_list重载,std::make_shared<t>({a,b,c})</t>会匹配它;但std::shared_ptr<t>(new T({a,b,c}))</t>也一样,这点无差异 - 不支持
explicit构造函数的隐式转换——这和普通构造一致,不是陷阱,只是需确认是否真要显式调用
性能差异在什么规模下才值得关心
单次调用差异微乎其微,但高频路径(如每帧创建几十个对象的游戏逻辑、网络服务中每个请求分配上下文)下,内存分配次数减半 + 更好的局部性,可带来可观收益。用 perf 或 valgrind --tool=massif 能观察到控制块分配显著减少。
不过,如果 T 本身很大(比如含大数组或缓存),那对象分配开销远超控制块,此时优化重点就该放在对象布局或复用上,而不是纠结 make_shared。
真正容易被忽略的是:即使你写了 std::make_shared,如果后续又用它构造另一个 std::shared_ptr(比如赋值、拷贝、作为参数传入),控制块不会重复分配,但“只分配一次”的优势只体现在首次创建环节。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










