std::make_unique是c++14引入的安全工厂函数,用于构造std::unique_ptr,避免new与unique_ptr手动组合导致的异常安全问题和资源泄漏;它不支持自定义删除器、数组类型、不完全类型或placement new等场景。

std::make_unique 是什么,它和 new 有什么区别
它是一个工厂函数,用来安全构造 std::unique_ptr 所管理的对象。本质是把 new 和 unique_ptr 的绑定过程封装成一步,避免裸指针中转。
常见错误现象:手写 std::unique_ptr<t>(new T(args...))</t> 看似等价,但可能引发资源泄漏——尤其当构造函数抛异常、或参数求值顺序不确定时(比如多个 new 表达式同时出现在一个函数调用里)。
实操建议:
- 永远优先用
std::make_unique<t>(args...)</t>,而不是new+ 构造unique_ptr -
std::make_unique不支持自定义删除器(deleter),需要时只能退回到显式构造std::unique_ptr - 它不支持数组类型初始化(如
std::make_unique<int>(10)</int>是合法的,但std::make_unique<int>()</int>不行;后者需用std::unique_ptr<int></int>显式构造)
为什么在异常安全中“必不可少”
关键不在“它自己抛异常”,而在它消除了“部分构造成功却无法回滚”的风险。C++ 标准规定:函数参数按未指定顺序求值,若某参数含 new,另一参数构造抛异常,则已分配的内存无法被自动释放。
立即学习“C++免费学习笔记(深入)”;
使用场景:函数接受多个 std::unique_ptr 参数,或作为其他对象成员初始化的一部分。
示例对比:
// 危险!可能泄漏 f(std::unique_ptr<A>(new A), std::unique_ptr<B>(new B())); // 若 B() 抛异常,A 的内存永久丢失 // 安全!两个 new 都由 make_unique 内部完成,且受同一异常边界保护 f(std::make_unique<A>(), std::make_unique<B>()); // 要么都成,要么都败,无泄漏
性能影响:零开销抽象——std::make_unique 是纯转发,编译后和手写 new + unique_ptr 构造几乎一致;但异常安全收益是免费的。
哪些情况不能用 std::make_unique
它不是万能替代品。以下情形必须绕过它,直接构造 std::unique_ptr:
- 需要自定义删除器:
std::unique_ptr<foo mydeleter>(new Foo, MyDeleter{})</foo> - 从已有裸指针接管所有权(例如 C API 返回的指针):
std::unique_ptr<file>(fopen(...), fclose)</file> - 构造不完全类型(incomplete type),比如前向声明类,此时
make_unique无法得知 sizeof,会编译失败 - 需要延迟构造(placement new 场景),
make_unique总是调用默认分配器和默认构造
兼容性与陷阱:C++11/14/17 的差异
std::make_unique 是 C++14 引入的,C++11 标准库并不提供。很多项目仍用 C++11 工具链,或误以为头文件包含 <memory></memory> 就一定有它。
常见错误现象:error: 'make_unique' is not a member of 'std',即使写了 #include <memory></memory>。
实操建议:
- 确认编译器标准:GCC 4.9+、Clang 3.4+、MSVC 2015+ 支持 C++14 及以上才默认启用
std::make_unique - 不要依赖宏判断
__cplusplus值(它在 GCC 中常不准确),改用__cpp_lib_make_unique特性宏检测 - 若必须支持 C++11,可自行实现最小可用版(仅支持单参数、无删除器、非数组),但注意别和标准库冲突
最易被忽略的一点:即使编译通过,也要检查是否所有 make_unique 调用都落在异常安全的关键路径上——漏掉一处,整条链就退回裸指针风险模型。










