std::make_shared 比 new + shared_ptr 构造更快,因为它将控制块和对象分配在同一块内存中,避免两次 malloc、提升缓存友好性;但不支持自定义删除器、别名指针等场景。

std::make_shared 为什么比 new + shared_ptr 构造更快
因为 std::make_shared 把控制块(control block)和对象本身分配在**同一块内存里**,而 new + shared_ptr 构造要分别 malloc 两次:一次给对象、一次给控制块。少了内存分配次数,也减少了缓存不友好访问。
实操建议:
- 只要构造对象不需要自定义删除器或别名指针(aliasing),就优先用
std::make_shared - 对象类型必须可移动或可复制(否则可能触发额外拷贝)
- 如果类的构造函数是 explicit 的,
std::make_shared仍能正常调用——它不依赖隐式转换
哪些情况不能用 std::make_shared
不是所有 shared_ptr 初始化场景都适用。典型限制有:
- 需要自定义删除器(比如用
fclose关闭FILE*):std::make_shared不接受删除器参数 - 想用别名指针(aliasing constructor),例如让
shared_ptr<int>指向某结构体内嵌字段 - 对象类型没有 public 构造函数(如只有 friend 工厂函数),且该工厂返回的是裸指针
- 需要捕获异常后做特殊处理——
make_shared内部 new 失败会直接 throwstd::bad_alloc,无法干预
std::make_shared 的参数转发陷阱
它用的是完美转发(perfect forwarding),所以传入的参数会被原样转发给构造函数。这容易导致意外行为:
立即学习“C++免费学习笔记(深入)”;
- 传入左值引用时,可能绑定到非预期的重载(比如你传了
std::string& s,但类有个接受const char*的构造函数,编译器可能选错) - 传入临时对象(如
std::string{"hello"})没问题;但传入已命名变量时,注意是否发生拷贝或移动 - 如果类构造函数有默认参数,
make_shared依然生效,但要注意默认值是在调用点求值,不是在类定义处
示例:
struct A { A(int, std::string = "default") {} };<br>auto p = std::make_shared<A>(42); // ✅ 合法,"default" 在此求值
和 std::shared_ptr 的初始化方式对比
下面三种写法语义不同,性能和行为也有差异:
-
std::shared_ptr<T>(new T{args...}):两段内存分配,无异常安全保证(new 成功但 control block 分配失败时,裸指针泄露) -
std::shared_ptr<T>(new T{args...}, custom_deleter):支持删除器,但仍是两次分配 -
std::make_shared<T>(args...):一次分配,异常安全,但不支持删除器或别名
如果你写了 std::shared_ptr<T> p(new T{1,2,3});,编译器不会警告,但这是过时且低效的习惯。
真正难处理的点在于:当你要把一个已有裸指针(比如来自 C API)转成 shared_ptr,make_shared 根本不适用——这时候只能老实用 shared_ptr 构造函数,并确保删除器正确。









