std::make_shared 更优因其一次分配同时构建控制块和对象,避免两次分配的性能损耗、缓存不友好及异常安全问题;要求t有可访问构造函数且非数组,不调用重载operator new。

std::make_shared 为什么比 new + shared_ptr 构造更优
因为 std::make_shared 在一次内存分配中同时构造控制块和对象,而 shared_ptr<t>(new T(args...))</t> 至少需要两次分配(一次给对象,一次给控制块),性能差、缓存不友好,还可能引发异常安全问题——如果控制块分配失败,new T 已执行却无法回滚。
实操建议:
- 只要能用
std::make_shared,就别手写shared_ptr构造函数 - 它要求类型
T必须有可访问的构造函数(包括 public 或 friend),且不能是数组类型(make_shared<int></int>非法) - 注意:若类重载了
operator new,make_shared不会调用它——它使用的是全局或std::allocator分配方式
哪些场景下 std::make_shared 会失效或报错
常见编译错误如 error: no matching function for call to 'make_shared',通常源于以下原因:
- 传入参数无法匹配
T的任何构造函数(比如私有构造、explicit 构造函数未显式转型) -
T是抽象类或含删除/不可访问的拷贝/移动构造函数(但只要能构造实例,一般不影响make_shared) - 试图用
make_shared构造带 private 继承或友元限制的类,而调用点无权访问构造函数 - 模板推导失败:比如
make_shared<:vector>>(10, 42)</:vector>没问题,但make_shared<:vector>>({1,2,3})</:vector>可能因初始化列表类型推导失败而报错(需加std::initializer_list显式标注)
std::make_shared 初始化时的参数转发陷阱
make_shared 内部用完美转发(std::forward)传递参数,这意味着:
立即学习“C++免费学习笔记(深入)”;
- 右值会被移走,原变量后续状态不确定(尤其对
std::move过来的临时对象要小心) - 引用类型参数若绑定到局部变量,而该变量在
make_shared返回前已析构,会导致悬垂引用(虽然少见,但在 lambda 捕获或自定义分配器中可能出现) - 避免把非 const lvalue 引用传给期望右值的构造函数——编译器不会自动转成右值,得手动
std::move
例如:auto p = std::make_shared<:string>(s);</:string> 是拷贝构造;auto p = std::make_shared<:string>(std::move(s));</:string> 才触发移动。
与 std::shared_ptr 构造函数的行为差异细节
两者语义并不完全等价,容易被忽略的点:
-
std::make_shared<t>(args...)</t>等价于shared_ptr<t>(new T(std::forward<args>(args)...))</args></t>,不是shared_ptr<t>(new T{args...})</t>—— 它用的是圆括号构造,不是花括号初始化(即不触发std::initializer_list优先匹配) - 若
T有explicit构造函数,make_shared仍可调用;但若误写成make_shared<t>({a,b})</t>,可能意外匹配到initializer_list版本,而你本意是普通构造 - 自定义删除器无法通过
make_shared设置(它只支持默认删除器),必须用shared_ptr构造函数
真正需要自定义删除器、或必须复用已有裸指针时,才放弃 make_shared。其余情况,它就是更安全、更快、更简洁的选择。











