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<t>(new T(args...))</t>可能导致new T成功但控制块分配失败时的内存泄漏 - 减少一次内存分配:尤其对小对象,性能提升明显
- 不支持自定义删除器:如果需要
std::shared_ptr持有非默认释放逻辑(比如 C 风格资源),就不能用std::make_shared,必须显式构造
std::make_shared 的参数转发规则和常见陷阱
std::make_shared 使用完美转发(perfect forwarding),所以参数行为和直接调用 T 的构造函数一致 —— 但要注意引用折叠和临时对象生命周期。
- 传递左值时会被当作左值引用转发,若
T构造函数只接受右值(如移动构造),会编译失败 - 不要传入临时字符串字面量给接受
std::string&&的构造函数:例如std::make_shared<foo>("hello")</foo>中,"hello"是const char[6],不是std::string,不会触发移动,而是调用std::string(const char*)构造,没问题;但如果Foo构造函数形参是std::string&& s,则无法绑定字面量 - 避免在参数中隐式构造大对象:比如
std::make_shared<heavy>(Heavy())</heavy>会先构造临时对象再移动,不如直接让Heavy的构造函数接收初始化参数
auto ptr = std::make_shared<std::vector<int>>(1000, 42); // ✅ 推荐:直接传 vector 构造参数 auto ptr2 = std::make_shared<std::vector<int>>(std::vector<int>(1000, 42)); // ❌ 不必要拷贝/移动
不能用 std::make_shared 的典型场景
以下情况必须绕过 std::make_shared,改用 std::shared_ptr 的显式构造:
立即学习“C++免费学习笔记(深入)”;
- 类有私有或受保护的构造函数,且未将
std::make_shared声明为友元(此时编译失败) - 需要自定义删除器(如关闭文件描述符、释放 C API 资源):
std::shared_ptr<file>(fopen(...), [](FILE* f) { fclose(f); })</file> - 需要指向对象的指针不是原始
new分配的地址(例如 placement new、内存池、mmap 区域) - 类重载了
operator new,但你希望控制块和对象分离分配(极少见,通常是为了调试或特殊内存布局)
真正容易被忽略的是:即使你写了 std::make_shared<t>(args...)</t>,如果 T 的构造函数本身抛异常,控制块和对象内存仍会由 std::make_shared 自动清理 —— 这部分不用操心;但若你在构造函数里手动 new 了其他资源且没做 RAII 封装,那还是得自己处理。











