make_unique
在 C++ 中,make_unique
是 C++14 引入的一个辅助函数,用于更安全、更便捷地创建 std::unique_ptr
智能指针。相较于直接使用 new
表达式初始化 unique_ptr
,make_unique
的安全性主要体现在以下几个方面:
1. 避免资源泄漏(异常安全)
直接使用 new
的风险
当用 new
手动创建对象并传递给智能指针时,若初始化过程中发生异常(例如构造函数抛出异常或后续代码出错),可能导致资源未被智能指针接管而泄漏。
示例(风险场景):
// 假设 func() 可能抛出异常
std::unique_ptr<int> p(new int(42)); // 安全:直接初始化
std::unique_ptr<int> p1(new int(42), func); // 安全:deleter是栈上对象
std::unique_ptr<int> p2(new int(42), std::bind(func, get_data())); // 危险!
- 在
std::unique_ptr<int> p2(...)
中,表达式执行顺序为:- 执行
get_data()
获取参数(可能抛出异常)。 - 执行
new int(42)
分配内存(可能抛出bad_alloc
)。 - 执行
std::bind(func, ...)
创建deleter。 - 将指针和deleter传入
unique_ptr
构造函数。
- 执行
- 风险点:若步骤1或步骤3抛出异常,
new int(42)
返回的指针尚未被unique_ptr
接管,导致内存泄漏。
make_unique
的安全性
make_unique
通过单一表达式完成内存分配、对象构造和智能指针对象的创建,确保在异常发生时资源能被正确释放。
示例(安全版本):
auto p = std::make_unique<int>(42); // 等价于 unique_ptr<int>(new int(42))
auto p2 = std::make_unique<int>([](int* x){ func(x); }); // 自定义deleter
make_unique
内部会直接将new
表达式的结果传递给unique_ptr
构造函数,保证内存分配和智能指针初始化在同一作用域内完成,避免中间步骤的异常导致泄漏。
2. 防止裸指针误用
直接使用 new
的隐患
手动使用 new
时,可能意外将裸指针暴露给外部,破坏智能指针的封装性,导致悬空指针或双重释放问题。
示例(错误用法):
int* raw = new int(42);
std::unique_ptr<int> p1(raw);
std::unique_ptr<int> p2(raw); // 非法!两个智能指针管理同一裸指针,导致双重释放
make_unique
的封装性
make_unique
直接返回 unique_ptr
对象,不暴露裸指针,从源头避免上述问题。
示例(安全用法):
auto p1 = std::make_unique<int>(42);
auto p2 = p1; // 非法!unique_ptr不可拷贝,避免误操作
auto p3 = std::move(p1); // 合法!通过移动语义转移所有权
3. 更简洁的语法,减少人为错误
直接使用 new
的冗余
初始化 unique_ptr
时需显式写出类型,容易因类型不匹配导致编译错误。
示例(冗余代码):
std::unique_ptr<std::vector<int>> vec(new std::vector<int>{1, 2, 3});
// 需重复书写类型 `std::vector<int>`
make_unique
的类型推导
make_unique
支持模板类型推导,代码更简洁且不易出错。
示例(类型推导):
auto vec = std::make_unique<std::vector<int>>({1, 2, 3});
// 自动推导为 unique_ptr<std::vector<int>>,无需重复书写类型
4. 对数组和聚合类型的友好支持
数组初始化(C++14+)
make_unique
支持直接创建数组类型的 unique_ptr
,避免手动处理 []
的问题。
示例:
// 分配包含5个int的数组(值初始化为0)
auto arr = std::make_unique<int[]>(5); // 等价于 unique_ptr<int[]>(new int[5]())
arr[0] = 10;// 列表初始化(C++17+)
auto arr2 = std::make_unique<int[]>({1, 2, 3}); // 数组大小为3
聚合类型初始化(C++17+)
对于没有构造函数的聚合类型(如 struct
),make_unique
支持直接初始化成员。
示例:
struct Point { int x, y; }; // 聚合类型
auto p = std::make_unique<Point>(Point{1, 2}); // C++14写法
auto p2 = std::make_unique<Point>(1, 2); // C++17简化写法(直接传递成员值)
5. 与其他智能指针和库的兼容性
make_unique
是 C++ 标准库的一部分,与 std::make_shared
(用于 shared_ptr
)风格统一,便于代码维护和团队协作。
- 例如,在需要返回智能指针的函数中,使用
make_unique
可确保接口的一致性:std::unique_ptr<MyClass> createObject() {return std::make_unique<MyClass>(arg1, arg2); // 简洁且安全 }
总结:make_unique
的核心优势
特性 | make_unique | 直接使用 new + unique_ptr |
---|---|---|
异常安全 | 单一表达式完成分配和初始化,避免泄漏 | 可能因初始化顺序导致泄漏 |
封装性 | 不暴露裸指针,禁止非法拷贝 | 可能意外传递裸指针,导致双重释放 |
语法简洁性 | 自动类型推导,减少冗余 | 需显式指定类型,容易出错 |
数组/聚合支持 | 原生支持数组和聚合类型初始化 | 需手动处理 [] 和构造函数 |
代码一致性 | 与 make_shared 风格统一,易于维护 | 风格不统一,可能混合裸指针和智能指针 |
何时不使用 make_unique
?
- 需要自定义deleter的非默认构造场景:
若deleter需要在unique_ptr
构造时动态生成(如依赖外部参数),仍需手动使用new
。
示例:// 自定义deleter依赖外部函数指针 void(*deleter)(int*) = my_custom_deleter; auto p = std::unique_ptr<int>(new int(42), deleter); // 无法用make_unique直接生成,需手动处理
- C++14 之前的编译器:
若项目需兼容 C++11,需手动实现类似make_unique
的辅助函数(C++11 标准库未包含make_unique
,但可自行实现)。
最佳实践
- 优先使用
make_unique
:只要场景允许,尽量用make_unique
创建unique_ptr
,避免手动操作new
。 - 结合RAII原则:将资源管理完全交给智能指针,避免在作用域内显式调用
delete
。 - 避免裸指针传递:函数参数和返回值尽量使用智能指针或引用,减少裸露资源的风险。
通过 make_unique
,C++ 提供了一种更安全、更现代的动态内存管理方式,显著降低了传统 new/delete
模式下的常见错误。