make_unique和make_shared通过原子化内存分配与构造、RAII自动清理,避免异常导致的资源泄漏;且make_shared合并控制块与对象内存,减少一次分配。

为什么 make_unique 和 make_shared 能避免资源泄漏?
因为 new 表达式本身不提供异常安全保证——当构造函数抛出异常时,已分配但未管理的裸指针会直接丢失,导致内存泄漏。
而 make_unique 和 make_shared 是原子操作:内存分配和对象构造在单个函数调用内完成,且内部使用 RAII 包装;一旦构造失败,它们会自动释放已分配内存。
-
std::unique_ptr:先p(new T(args...)) new T,再构造unique_ptr;若unique_ptr构造中途(如移动赋值)抛异常,T的内存就泄漏了 -
auto p = std::make_unique:整个过程在一个函数内,无中间裸指针状态(args...) - 同理适用于
make_shared,它还额外把控制块和对象内存合并分配,减少一次堆分配
make_shared 比 shared_ptr + new 少一次内存分配
shared_ptr 需要两块独立内存:一块存对象,一块存引用计数控制块。手动用 new 构造时,这两块必须分别申请。
make_shared 把对象和控制块布局在同一块连续内存里,仅需一次 malloc(或 operator new),提升缓存局部性并降低开销。
立即学习“C++免费学习笔记(深入)”;
- 典型场景:
auto sp = std::make_shared<:vector>>(1000)→ 1 次分配 - 等价手动写法:
std::shared_ptr<:vector>> sp(new std::vector→ 至少 2 次分配(对象 + 控制块)(1000)) - 注意:
make_shared不支持自定义删除器(custom deleter),此时只能退回到shared_ptr+new
哪些情况不能用 make_unique/make_shared?
不是所有构造场景都适用,硬套反而引发编译错误或语义偏差。
- 类有私有/删除的构造函数,且未将
make_*声明为友元(罕见但存在) - 需要传递自定义删除器:
std::unique_ptr→ 只能用std::unique_ptr(new Foo) - 想用
std::shared_ptr管理已有裸指针(如 C API 返回的指针)→ 必须用shared_ptr构造函数,无法用make_shared -
make_shared无法调用std::initializer_list构造函数(C++17 前),例如std::make_shared<:vector>>({1,2,3})会失败;需改用std::shared_ptr<:vector>>(new std::vector{1,2,3})
异常安全的典型反例:多个 make_* 参数传入同一函数时
即使每个 make_unique 单独是异常安全的,多个一起作为函数参数时,C++ 不规定求值顺序,仍可能泄漏。
void process(std::unique_ptr, std::unique_ptr); process(std::make_unique(), std::make_unique()); // ❌ 危险!
假设 make_unique() 先执行并成功,但 make_unique() 后执行时抛异常,则 B 的资源已分配却无人接管。
- 正确做法:分步构造,用变量承接
auto a = std::make_unique(); auto b = std::make_unique(); process(std::move(a), std::move(b));- 这是最容易被忽略的一环:
make_*解决的是「单次构造」的安全问题,不是「多参数表达式」的求值安全










