C++ std::make_unique详解-安全创建unique_ptr的官方方法
C++ std::make_unique详解-安全创建unique_ptr的官方方法
一、C++ std::make_unique详解
1、什么是 std::make_unique?
std::make_unique 是 C++14 标准库中引入的一个模板函数,用于创建并返回一个指向动态分配对象的 std::unique_ptr 智能指针。它的主要目的是提供一种更安全、更简洁的方式来创建和管理独占所有权的动态对象。
2、为什么需要 std::make_unique? (与直接 new 对比)
在 std::make_unique 出现之前,创建 std::unique_ptr 通常是这样做的:
std::unique_ptr<MyClass>ptr(newMyClass(arg1, arg2));这种方式存在一些潜在问题:
- 异常安全风险: 如果在
new操作和unique_ptr构造之间发生了异常(例如在计算构造函数参数时),那么new分配的内存可能无法被unique_ptr接管,从而发生内存泄漏。 - 代码冗余: 需要重复书写类型
MyClass。 - 风格一致性: C++11 提供了
std::make_shared来创建std::shared_ptr,缺少std::make_unique显得不完整。
std::make_unique 解决了这些问题:
auto ptr = std::make_unique<MyClass>(arg1, arg2);- 异常安全:
std::make_unique在内部一次性完成内存分配和对象构造。如果构造函数参数的计算过程中抛出异常,或者构造函数本身抛出异常,因为此时还没有返回unique_ptr,所以不会有内存泄漏(分配的内存会被自动释放)。 - 简洁性: 使用
auto关键字,避免了类型重复书写,代码更简洁。 - 一致性: 与
std::make_shared的使用方式保持一致。 - 效率: 编译器有机会进行优化。
3、基本语法和用法
std::make_unique 有两种主要形式:
- 数组版本返回的是
std::unique_ptr<T[]>,这与指向单个对象的std::unique_ptr<T>是不同的特化。 - 数组元素会被值初始化(基本类型初始化为 0,类类型调用默认构造函数)。无法在创建数组时指定每个元素的构造参数。
- 访问数组元素使用
operator[]:arr[i] = 42;。
创建动态数组:
template<typenameT> std::unique_ptr<T[]> std::make_unique(std::size_t size);用法:
// 创建一个包含 10 个默认构造的 int 的动态数组auto arr = std::make_unique<int[]>(10);// 创建一个包含 5 个默认构造的 Widget 的动态数组auto widgetArr = std::make_unique<Widget[]>(5);注意:
创建单个对象:
template<typenameT,typename... Args> std::unique_ptr<T> std::make_unique(Args&&... args);用法:
// 创建默认构造的 Widgetauto widget1 = std::make_unique<Widget>();// 创建带参数的 Widgetauto widget2 = std::make_unique<Widget>(100,"Hello");// 创建派生类对象 (多态)auto derived = std::make_unique<Derived>(some_arg); std::unique_ptr<Base> basePtr = std::move(derived);// 所有权转移4、关键特性
- 所有权独占:
std::make_unique创建的std::unique_ptr拥有对所创建对象的独占所有权。这意味着同一时刻只有一个unique_ptr可以指向该对象。所有权可以通过std::move转移。 - 自动内存管理: 当
unique_ptr被销毁(例如离开作用域)时,它所管理的对象会被自动删除(调用其析构函数并释放内存)。 - 不支持自定义删除器:
std::make_unique不支持指定自定义删除器。如果需要自定义删除器,必须直接使用std::unique_ptr的构造函数,例如std::unique_ptr<T, D> ptr(new T, custom_deleter);。 - 不能用于
std::shared_ptr:std::make_unique只创建std::unique_ptr。要创建std::shared_ptr,应使用std::make_shared。
5、总结与建议
- 优先使用
std::make_unique: 在 C++14 及以后的代码中,优先使用std::make_unique来创建std::unique_ptr。它提供了更好的异常安全性和更简洁的代码。 - 理解所有权: 清楚
unique_ptr的独占所有权语义,并通过std::move来转移所有权。 - 数组特化: 需要创建动态数组时,使用
std::make_unique<T[]>(size)形式。 - 自定义删除器的限制: 如果需要自定义删除器,则不能使用
std::make_unique。
二、示例代码
1、示例代码1
#include<iostream>#include<memory>#include<string>classMyClass{public:MyClass(int id, std::string name):id_(id),name_(std::move(name)){ std::cout <<"MyClass constructed: "<< id_ <<", "<< name_ <<"\n";}~MyClass(){ std::cout <<"MyClass destroyed: "<< id_ <<", "<< name_ <<"\n";}voidprint()const{ std::cout <<"ID: "<< id_ <<", Name: "<< name_ <<"\n";}private:int id_; std::string name_;};intmain(){// 创建单个对象auto obj = std::make_unique<MyClass>(1,"Alice"); obj->print();// 创建动态数组 (5 个 int)auto numbers = std::make_unique<int[]>(5);for(int i =0; i <5;++i){ numbers[i]= i * i; std::cout << numbers[i]<<" ";} std::cout <<"\n";// 所有权转移auto newOwner = std::move(obj);if(!obj){ std::cout <<"obj is now nullptr\n";} newOwner->print();// 离开作用域时,newOwner 和 numbers 管理的对象会被自动销毁return0;}输出:
MyClass constructed: 1, Alice ID: 1, Name: Alice 0 1 4 9 16 obj is now nullptr ID: 1, Name: Alice MyClass destroyed: 1, Alice 
2、示例代码2
#include<memory>// 包含 std::make_unique 和 std::unique_ptr#include<iostream>// 定义一个简单的自定义类classPoint{public:Point(int x,int y):x_(x),y_(y){}voidprint()const{ std::cout <<"Point("<< x_ <<", "<< y_ <<")\n";}private:int x_;int y_;};intmain(){// 示例 1: 创建一个 std::unique_ptr 指向 int 类型auto num_ptr = std::make_unique<int>(42);// 使用 std::make_unique 分配内存 std::cout <<"整数值: "<<*num_ptr << std::endl;// 解引用指针输出值// 示例 2: 创建一个 std::unique_ptr 指向自定义类 Pointauto point_ptr = std::make_unique<Point>(3,4);// 传递构造函数参数 point_ptr->print();// 调用成员函数return0;}输出:
整数值:42Point(3,4)