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


















