std::make_unique能保证异常安全,因其将内存分配、对象构造、智能指针封装三步绑定为原子操作;而new配合std::unique_ptr在参数求值或构造异常时易致内存泄漏。

std::make_unique 在内存分配失败时自动抛出 std::bad_alloc,且绝不会留下裸指针或泄漏内存;而直接用 new 搭配 std::unique_ptr 构造,在参数求值或构造函数抛异常时,可能造成已分配内存无法回收。
std::make_unique 为什么能保证异常安全
关键在于「原子性」:它把内存分配、对象构造、智能指针封装三步绑成一个不可分割的操作。哪怕 MyClass 的构造函数抛出异常,std::make_unique 内部已分配的内存也会立即释放。
对比之下,std::unique_ptr 实际是两步:
-
new MyClass(...)先执行 —— 内存已分配,对象开始构造 - 若
arg1或arg2计算过程抛异常(比如另一个std::make_unique失败),new返回的指针根本没传给std::unique_ptr构造函数,内存就此泄漏
new 表达式在多参数调用中的陷阱
当函数接受多个 std::unique_ptr 参数时,C++ 不规定参数求值顺序。这意味着:
process(std::unique_ptr(new A()), std::unique_ptr(new B()))- 若
B()构造失败抛异常,A()已成功分配并构造,但无人持有其指针 —— 泄漏发生 - 换成
process(std::make_unique(), std::make_unique()),任一失败都会让另一个已创建的std::unique_ptr正常析构
std::make_unique 不支持数组类型
这是它和 new[] 的明确分界线:
-
std::make_unique合法,生成(10) std::unique_ptr -
std::make_unique合法,但(5) MyClass必须有平凡析构函数(否则编译失败) -
std::make_unique不能用于带参数的数组元素构造(比如想对每个MyClass调用含参构造函数)—— 这种需求只能退回new MyClass[5]{MyClass(1), MyClass(2), ...},但要自己管理生命周期
类型推导与代码冗余问题
写 new 时类型必须写两遍:
std::unique_ptr<:vector>> p(new std::vector<:string>{ "a", "b" });- 而
auto p = std::make_unique<:vector>>("a", "b");更短,且改类型时只改一处 - 更隐蔽的问题:
std::unique_ptr是合法的,但(new Derived()) std::make_unique无法隐式转为() std::unique_ptr—— 编译失败反而帮你提前发现设计问题
真正容易被忽略的是:即使你从不显式 throw,new 本身在内存不足时就会抛 std::bad_alloc,而手写 new + std::unique_ptr 的组合,会让这个异常点变成资源管理的断裂面。不是所有异常都来自业务逻辑,内存分配失败就是最现实的一种。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











