std::make_shared 更高效因一次分配控制块与对象内存,避免两次分配导致的缓存不友好、cpu预取失效及内存争用;但不支持自定义删除器、别名构造、私有构造(无友元)、数组类型及自定义分配器(需用allocate_shared)。

std::make_shared 为什么比 new + std::shared_ptr 构造更高效
因为 std::make_shared 能把控制块(control block)和对象内存一次分配完成,而 new 配合 std::shared_ptr 构造器要分配两次:一次给对象,一次给控制块。
这不只是“少一次 malloc”的问题——两次分配会增加缓存不友好性、影响 CPU 预取,还可能在高并发下加剧内存分配器争用。
- 典型开销对比:
std::shared_ptr<t>(new T(args...))</t>→ 2 次堆分配;std::make_shared<t>(args...)</t>→ 1 次堆分配(内部按需对齐并布局控制块+对象) - 仅当
T的构造函数抛异常时,std::make_shared的单次分配优势才会失效(此时控制块和对象都未构造成功,仍安全) - 注意:如果
T有自定义operator new,std::make_shared不会调用它——它走的是全局::operator new或std::allocator分配路径
哪些情况不能用 std::make_shared 替代 new
不是所有 std::shared_ptr 创建场景都适用 std::make_shared,最常见的是需要自定义删除器或别名构造的场合。
- 需要非默认删除器:比如用
std::shared_ptr<int>(new int(42), [](int* p){ free(p); })</int>,std::make_shared不支持传入删除器 - 需要别名构造(aliasing constructor):如
std::shared_ptr<t>(ptr, other_ptr)</t>,用于共享控制块但指向不同子对象,make_shared无法实现 -
T的构造函数是私有/显式 delete,且没有友元允许make_shared访问(C++17 起,make_shared对私有构造的支持依赖于类是否将std::make_shared声明为友元) - 需要传递
std::weak_ptr到构造函数中(极少见,但某些循环引用规避模式会这样用)
std::make_shared 在 C++11/14/17 中的行为差异
主要差异集中在参数转发和异常安全性上,而不是内存布局逻辑本身。
立即学习“C++免费学习笔记(深入)”;
- C++11:完美转发参数,但若构造函数抛异常,控制块已分配却未被管理,存在微小泄漏风险(实际编译器普遍优化掉了)
- C++14 起:标准明确要求异常安全——分配失败或构造失败时,控制块必须被正确释放,无需担心泄漏
- C++17:引入了
std::make_shared对std::array和聚合类型的更好支持,但普通类无变化;同时禁止编译器对make_shared做“返回值优化”以外的额外优化(保证控制块与对象同分配) - 所有版本都不支持数组类型直接构造:
std::make_shared<int>()</int>是非法的,必须用std::shared_ptr<int>(new int[10])</int>
容易被忽略的坑:自定义分配器 + make_shared 的兼容性
很多人以为加个 std::allocator_arg 就能用自定义分配器调 make_shared,但事实是:标准库至今(C++20)仍未提供带分配器的 make_shared 重载。
-
std::make_shared内部始终使用std::allocator<t></t>或全局::operator new,无视你传进来的任何分配器实例 - 如果你真需要分配器感知的
shared_ptr,只能手动做两步:auto alloc = MyAlloc<t>{}; auto ptr = std::allocate_shared<t>(alloc, args...)</t></t>—— 注意是std::allocate_shared,不是make_shared -
std::allocate_shared行为和make_shared几乎一致,只是把分配器传给了控制块和对象的联合分配过程 - 别名构造、自定义删除器这些限制,
allocate_shared同样不支持
事情说清了就结束。










