悠泥客指针

unique_ptr

1. 简介

C++ 为编程人员提供了3种智能指针--std::shared_ptr, std::unique_ptr, std::weak_ptr. 本文先介绍 std::unique_ptr, 该类型的指针能够管理堆上的对象, 同时在适当时候(作用域外)释放内存. 应当说明 unique_ptr 定义在头文件 中. 下面是其模板的定义:

1
2
3
4
5
6
7
8
9
template<
class T,
class Deleter = std::default_delete<T>
> class unique_ptr;
///
template<
class T,
class Deleter
> class unique_ptr<T[], Deleter>;
当如下操作之一执行的时候, 使用对应的删除器将对象释放:

  1. unique_ptr 所管理的对象被销毁
  2. 通过重写的赋值运算 operator= 或者 公有成员函数 reset()unique_ptr 管理的对象赋给其他指针.

调用 get_deleter()(ptr) 使用用户提供的删除器释放 unique_ptr 对象. 默认的删除器要使用 delete 运算符来销毁对象并释放对象占据的内存.

当然, unique_ptr 也可以不指向任何对象, 此时即为空.

unique_ptr 管理对象的方法有两种:

  1. 使用 new 创建单个对象
  2. 使用 new[] 管理动态分配的对象数组(释放内存也要使用对应的删除器).

优雅使用 unique_ptr 的注意事项:

可以想见的是, 对象所有权只能在 non-const unique_ptr 之间进行转移. 如果对象是由 const std::unique_str 来管理的, 那该对象只受作用域的限制. unique_ptr 通常用来管理对象的生存期, 包括:

  1. 通过确保在(程序)正常退出和抛出异常退出两种情况下都能正常删除对象, 为处理具有动态生存期的对象的类或者函数提供一种异常安全的机制.
  2. 将独占的具有动态生存期的对象传递到函数中.
  3. 从函数获取独占的具有动态生存期的对象.
  4. 可以作为可移动容器的元素类型, 比如保有指向动态分配对象的指针的容器 std::vector.

std::unique_ptr 可为不完整类型 T 构造, 例如可以用于改善 plmpl 方法中句柄的用途. 若使用默认删除器, 在调用点处, T 必须是完整的类型, 例如在调用析构函数, 使用移动赋值运算符以及调用 std::unique_ptr 的成员函数 reset() 的时候. 相反地, 不能将 std::shared_ptr 从原始指针 (raw pointer) 构造成不完整类型, 但是当 T 是不完整类型时可以被销毁. 应当注意的是, 如果 T 是一个类模板的特化, 则 std::unique_ptr 作为操作数使用, 比如, 由于所谓的 Argument-dependent lookup (ADT), !p 要求 T 的参数必须是完整类型.

如果 T 是 B 的派生类, 那么 std::unique_ptr<T> 是可以隐式转换成 std::unique_ptr<B> 的. 经过隐式转换后形成的 std::unique_ptr<B> 的默认删除器将会调用 B 自身的删除器 (delete, 或者 delete[]), 这将会导致未定义的行为 (undefined behavior), 除非 B 的析构函数是虚函数. 还应当注意的是, std::shared_ptr 就不同了, 转换后的 std::shared_ptr<B> 将仍旧使用 T 的删除器, 不论 B 的析构是否为虚函数, 也会正确删除被占有对象.

不同于 std::shared_ptr, std::unique_ptr 能够通过任何满足 NullablePointer 的自定义句柄类型管理对象. 这就允许, 通过提供宏定义指针 boost::offset_ptr 或者其他奇特指针 (fancy pointer -- 提供指针抽象的指针) 的删除器, 来管理位于共享内存中的对象.

2. 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <cassert>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <memory>
#include <stdexcept>

// 说明运行时多态机制的基类
struct B
{
virtual ~B() = default;

virtual void bar() { std::cout << "B::bar\n"; }
};

// 派生类
struct D : B
{
D() { std::cout << "D::D\n"; }
~D() { std::cout << "D::~D\n"; }

void bar() override { std::cout << "D::bar\n"; }
};

// 接收 unique_ptr 为参数, 可以是值传递, 也可以是右值引用传递
std::unique_ptr<D> pass_through(std::unique_ptr<D> p)
{
p->bar();
return p;
}

int main()
{
std::cout << "1) Unique ownership semantics demo\n";
{
// 创建一个 p 独占的对象
std::unique_ptr<D> p = std::make_unique<D>();

// 通过函数 pass_through 转移所有权
std::unique_ptr<D> q = pass_through(std::move(p));

// `p` 现在为 `nullptr`
assert(!p);
}

std::cout << "\n"
"2) Runtime polymorphism demo\n";
{
// 创建一个基类型的指针p, 指向派生类型的对象
std::unique_ptr<B> p = std::make_unique<D>();

// 运行时多态, 因为指向的是派生类的对象
// 理应调用派生类的成员函数
p->bar();
}

std::cout << "\n"
"3) Array form of unique_ptr demo\n";
{
std::unique_ptr<D[]> p(new D[3]);
} // 析构 ~D() 会被调用三次

}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1) Unique ownership semantics demo
D::D
D::bar
D::~D

2) Runtime polymorphism demo
D::D
D::bar
D::~D

3) Array form of unique_ptr demo
D::D
D::D
D::D
D::~D
D::~D
D::~D


悠泥客指针
https://socod.github.io/2022/05/d9b2728991b4/
作者
socod
发布于
2022年5月10日
许可协议