std::make_shared比直接使用new配合std::shared_ptr更高效,因为它通过一次内存分配同时创建对象和控制块,减少开销、提升缓存局部性并增强异常安全;而new方式需两次分配,性能较低且存在异常安全隐患;但当需要自定义删除器、构造函数非公开或存在weak_ptr长期持有场景时,只能或应慎用std::make_shared,此时可选择new方式以获得更大灵活性,总结:常规场景优先使用std::make_shared,特殊需求下退回到new并注意资源管理问题。

std::make_shared和直接使用
new配合
std::shared_ptr构造在功能上都能实现共享所有权的智能指针,但它们在性能、异常安全和内存管理方面存在显著差异。下面从内存分配、性能优势和实际使用角度详细分析两者的区别。
1. 内存分配方式不同
当你使用
new创建对象并传递给
std::shared_ptr时:
std::shared_ptrptr(new Foo());
这会执行 两次独立的内存分配:
- 一次是
new Foo()
为对象本身分配内存; - 一次是
std::shared_ptr
内部为了管理引用计数(control block),需要额外分配一块内存来存储引用计数、弱引用计数等元信息。
而使用
std::make_shared:
auto ptr = std::make_shared();
它会 一次性分配一块连续内存,同时包含:
- 对象实例的数据;
- 控制块(引用计数等管理信息)。
这种优化称为 内存共置(allocation elision) 或 合并分配(combined allocation)。
2. 性能优势对比
✅ std::make_shared
的优势:
减少内存分配次数:从两次变为一次,显著降低动态内存分配的开销(
malloc
/free
是昂贵操作)。提高缓存局部性:对象和控制块在内存中相邻,访问时更可能命中同一缓存行,提升性能。
更快的构造速度:由于只做一次分配,且内部使用完美转发构造对象,效率更高。
-
异常安全更好:考虑如下代码:
// 潜在问题:如果 make_shared 之前抛异常,可能造成资源泄漏 functionThatTakesSharedPtr(std::shared_ptr
(new Foo), expensiveCall()); 参数求值顺序未定义,
new Foo
和expensiveCall()
哪个先执行不确定。如果expensiveCall()
抛异常,而new Foo
已执行但尚未交给shared_ptr
管理,则会发生内存泄漏。而使用
make_shared
是原子操作,避免此类问题。
❌ new + shared_ptr
的劣势:
- 多一次内存分配 → 更慢、更多碎片。
- 更差的缓存表现。
- 异常安全性差(如上所述)。
3. 什么时候不能用 make_shared
?
尽管
make_shared有很多优点,但在某些场景下无法使用:
(1)自定义删除器(custom deleter)
std::shared_ptrptr(new Foo, [](Foo* p){ /* custom cleanup */ });
std::make_shared不支持传入删除器。此时只能用
new。
(2)私有/受保护构造函数
如果类的构造函数不是公开的,
make_shared无法访问(因为它需要在内部构造对象),除非你提供友元或工厂方法。
(3)启用 weak_ptr
长期持有时的内存问题
make_shared将对象和控制块一起分配,这意味着:
- 即使对象早已析构(引用计数为0),
- 但如果还有
weak_ptr
指向它, - 那么那块合并的内存 无法释放,直到最后一个
weak_ptr
也被销毁。
这是因为控制块和对象共用一块内存。而
new + shared_ptr方式中,对象和控制块分开分配,对象可以在引用归零时立即释放,仅保留控制块供
weak_ptr使用。
所以,如果你预期对象很快析构但
weak_ptr会长时间存在,
make_shared可能导致内存延迟释放。
4. 实际建议
| 场景 | 推荐方式 |
|---|---|
| 普通对象创建,无特殊需求 | ✅ 优先使用 @@######@@ |
| 需要自定义删除器 | ❌ 必须用 @@######@@ + @@######@@ |
| 构造函数非 public | ❌ 通常不能用 @@######@@ |
| 对象生命周期短但 @@######@@ 长期存在 | ⚠️ 谨慎使用 @@######@@,考虑内存延迟释放问题 |
| 高性能关键路径 | ✅ 优先 @@######@@,减少分配和提升缓存效率 |
总结
std::make_shared
比new
更高效:一次分配、更好的缓存、异常安全。shared_ptr
方式更灵活:支持自定义删除器、特殊构造控制。- 在绝大多数常规场景下,应优先使用
make_shared
。 - 特殊需求下才退回到
weak_ptr
,并注意异常安全问题。
基本上就这些,不复杂但容易忽略细节。
make_shared
make_shared
make_shared
new + shared_ptr
new
std::make_shared
new











