智能指针unique_ptr<>创建的过程
两种初始化方式的比较
std::unique_ptr
可以通过两种方式进行初始化:直接构造或者使用 std::make_unique()
。它们之间的区别如下:
直接构造 std::unique_ptr
:
你可以通过直接构造来创建一个 unique_ptr
,如下:
std::unique_ptr<int> ptr(new int(42));
- 优点:
- 你可以在构造时精确控制对象的构造方式,比如分配自定义的内存管理器。
- 缺点:
- 这种方式需要手动调用
new
,可能会导致意外的内存泄漏。如果new
分配成功但在接下来分配std::unique_ptr
之前抛出异常,内存可能会泄漏。 - 手动调用
new
是一种旧的习惯用法,不符合现代 C++ 的推荐实践。
- 这种方式需要手动调用
使用 std::make_unique()
:
从 C++14 开始,标准库引入了 std::make_unique()
,这是现代 C++ 推荐的创建 unique_ptr
的方式:
auto ptr = std::make_unique<int>(42);
- 优点:
- 安全性:
make_unique
更加安全,因为它在单一表达式中创建对象并将其所有权交给unique_ptr
,不会出现手动调用new
时的内存泄漏问题。 - 简洁性:代码更简洁,不需要显式调用
new
,减少手动管理内存的风险。 - 异常安全性:在
make_unique
内部,内存分配和unique_ptr
的创建是原子操作。如果在创建过程中发生异常,内存会被自动清理,避免了内存泄漏。
- 安全性:
- 缺点:
- 在需要自定义的删除器或内存管理策略时,
make_unique
不如直接使用unique_ptr
构造函数灵活。
- 在需要自定义的删除器或内存管理策略时,
第一种方式的内部细节
-
调用
new int(42)
:new int(42)
会在堆上分配一个int
对象,并初始化其值为42
。这一步返回一个 原生指针,指向堆上的新对象。- 假设这个原生指针为
p_raw
,此时p_raw
是一个指向整数的裸指针。
int* p_raw = new int(42); // p_raw 是一个原生指针,指向堆上的整数对象
第一步的结果:
- 产生了一个原生指针
p_raw
,它指向堆上分配的int(42)
对象。
-
创建
std::unique_ptr<int> ptr
:- 接下来,
std::unique_ptr<int>
的构造函数被调用,接受该原生指针作为参数,并将其托管给unique_ptr
。 std::unique_ptr
接管了该原生指针的所有权,此时 原生指针 已经不再被使用,所有权转移到了std::unique_ptr
上。
std::unique_ptr<int> ptr(p_raw); // ptr 接管了 p_raw 的所有权
第二步的结果:
std::unique_ptr
接管了该指针的所有权,并在其生命周期内管理这个堆上的int
对象。该指针变成了unique_ptr
内部持有的指针。
- 接下来,
-
销毁原生指针:
- 在执行完
std::unique_ptr<int> ptr(new int(42));
这行代码后,堆上对象的所有权只归unique_ptr
,裸指针p_raw
不再存在(除非你显式声明了它)。
- 在执行完
过程总结:
-
两个指针:
- 第一步:当调用
new int(42)
时,产生了一个 原生指针,它指向新分配的int
对象。 - 第二步:
std::unique_ptr
的构造函数接受这个原生指针,并接管其所有权,之后该原生指针不再使用。
- 第一步:当调用
-
最终结果:
- 最终,只有一个指针(即
std::unique_ptr
内部持有的指针)管理这个堆上的对象。原生指针在unique_ptr
接管后不再需要使用。
- 最终,只有一个指针(即
标签:std,int,new,unique,ptr,指针 From: https://www.cnblogs.com/smartljy/p/18411601