c++中如何使用std::make_shared_c++创建共享指针的最佳实践【汇总】

std::make_shared 是正确写法,不存在 std::make_shared_c++ 等变体;它通过一次内存分配同时创建控制块和对象,更高效安全,但不支持自定义删除器或私有构造函数等场景。

std::make_shared 不存在,正确写法是 std::make_shared

这是最常被拼错的点。C++ 标准库中只有 std::make_shared,没有 std::make_shared_c++ 或类似变体。一旦编译器报错说 “not declared in this scope”,先检查拼写 —— 多半是手误多打了下划线或版本后缀。

为什么优先用 std::make_shared 而不是 new + shared_ptr 构造

std::make_shared 把对象构造和控制块(control block)内存分配合并在一次堆分配中,比分别调用 new 再传给 std::shared_ptr 构造函数更高效,也更安全。

  • 避免异常安全问题:若构造函数抛异常,std::make_shared 能保证控制块与对象内存一起释放;而 std::shared_ptr(new T(args...)) 可能导致 new T 成功但控制块分配失败时的内存泄漏
  • 减少一次内存分配:尤其对小对象,性能提升明显
  • 不支持自定义删除器:如果需要 std::shared_ptr 持有非默认释放逻辑(比如 C 风格资源),就不能用 std::make_shared,必须显式构造

std::make_shared 的参数转发规则和常见陷阱

std::make_shared 使用完美转发(perfect forwarding),所以参数行为和直接调用 T 的构造函数一致 —— 但要注意引用折叠和临时对象生命周期。

  • 传递左值时会被当作左值引用转发,若 T 构造函数只接受右值(如移动构造),会编译失败
  • 不要传入临时字符串字面量给接受 std::string&& 的构造函数:例如 std::make_shared("hello") 中,"hello"const char[6],不是 std::string,不会触发移动,而是调用 std::string(const char*) 构造,没问题;但如果 Foo 构造函数形参是 std::string&& s,则无法绑定字面量
  • 避免在参数中隐式构造大对象:比如 std::make_shared(Heavy()) 会先构造临时对象再移动,不如直接让 Heavy 的构造函数接收初始化参数
auto ptr = std::make_shared>(1000, 42); // ✅ 推荐:直接传 vector 构造参数
auto ptr2 = std::make_shared>(std::vector(1000, 42)); // ❌ 不必要拷贝/移动

不能用 std::make_shared 的典型场景

以下情况必须绕过 std::make_shared,改用 std::shared_ptr 的显式构造:

  • 类有私有或受保护的构造函数,且未将 std::make_shared 声明为友元(此时编译失败)
  • 需要自定义删除器(如关闭文件描述符、释放 C API 资源):std::shared_ptr(fopen(...), [](FILE* f) { fclose(f); })
  • 需要指向对象的指针不是原始 new 分配的地址(例如 placement new、内

    存池、mmap 区域)
  • 类重载了 operator new,但你希望控制块和对象分离分配(极少见,通常是为了调试或特殊内存布局)

真正容易被忽略的是:即使你写了 std::make_shared(args...),如果 T 的构造函数本身抛异常,控制块和对象内存仍会由 std::make_shared 自动清理 —— 这部分不用操心;但若你在构造函数里手动 new 了其他资源且没做 RAII 封装,那还是得自己处理。