C++ 智能指针(Smart Pointers)详细指南

1. 功能说明

本指南基于 smart_pointers.cpp 文件,详细介绍了 C++ 中智能指针的各种类型和用法,包括:

  • unique_ptr:独占所有权的智能指针,确保同一时间只有一个智能指针管理资源
  • shared_ptr:共享所有权的智能指针,使用引用计数管理资源
  • weak_ptr:弱引用智能指针,不增加引用计数,用于解决循环引用问题

智能指针是 C++11 引入的重要特性,它们能够自动管理动态内存,避免内存泄漏,提高代码的安全性和可靠性。本指南将详细介绍每种智能指针的使用方法、优缺点以及适用场景。

2. 代码解析

2.1 资源类定义

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
// 资源类,用于演示智能指针的使用
class Resource {
private:
std::string name;

public:
// 构造函数
Resource(const std::string& n) : name(n) {
std::cout << "Resource " << name << " created" << std::endl;
}

// 析构函数
~Resource() {
std::cout << "Resource " << name << " destroyed" << std::endl;
}

// 成员函数
void doSomething() const {
std::cout << "Resource " << name << " is doing something" << std::endl;
}

// 获取资源名称
std::string getName() const {
return name;
}
};

解析

  • 定义了一个 Resource 类,用于模拟需要动态管理的资源
  • 构造函数和析构函数都有输出信息,便于观察资源的创建和销毁
  • 提供了 doSomething() 方法和 getName() 方法,用于演示智能指针对资源的访问

2.2 unique_ptr(独占所有权智能指针)

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
std::cout << "=== unique_ptr(独占所有权智能指针)===" << std::endl;
{
// 创建 unique_ptr
std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>("A");
// 使用箭头运算符访问成员
ptr1->doSomething();
std::cout << "Resource name: " << ptr1->getName() << std::endl;

// 移动所有权
std::unique_ptr<Resource> ptr2 = std::move(ptr1);
// 检查 ptr1 是否为空
if (ptr1) {
std::cout << "ptr1 is valid" << std::endl;
} else {
std::cout << "ptr1 is null (moved to ptr2)" << std::endl;
}

// 检查 ptr2 是否有效
if (ptr2) {
ptr2->doSomething();
}

// 创建另一个 unique_ptr
std::unique_ptr<Resource> ptr3 = std::make_unique<Resource>("B");
// 重置 unique_ptr,会自动删除所管理的资源
ptr3.reset();
std::cout << "ptr3 reset" << std::endl;
}
std::cout << "End of unique_ptr scope" << std::endl;

解析

  • std::unique_ptr:独占所有权的智能指针,同一时间只能有一个 unique_ptr 管理资源
  • std::make_unique:C++14 引入的函数,用于创建 unique_ptr,比直接使用 new 更安全
  • std::move:转移 unique_ptr 的所有权,转移后原 unique_ptr 变为空
  • reset():重置 unique_ptr,会自动删除所管理的资源
  • 离开作用域时,unique_ptr 会自动删除所管理的资源

2.3 unique_ptr 与数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::cout << "\n=== unique_ptr 与数组 ===" << std::endl;
{
// 创建管理数组的 unique_ptr
std::unique_ptr<int[]> arr = std::make_unique<int[]>(5);
// 初始化数组元素
for (int i = 0; i < 5; ++i) {
arr[i] = i * 10;
}
// 打印数组元素
std::cout << "Array elements: ";
for (int i = 0; i < 5; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// 离开作用域时,unique_ptr 会自动删除数组
}

解析

  • std::unique_ptr<T[]>:用于管理数组的 unique_ptr 特化版本
  • 离开作用域时,会自动使用 delete[] 删除数组,而不是 delete

2.4 shared_ptr(共享所有权智能指针)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
std::cout << "\n=== shared_ptr(共享所有权智能指针)===" << std::endl;
{
// 创建 shared_ptr
std::shared_ptr<Resource> shared1 = std::make_shared<Resource>("Shared");
// 查看引用计数
std::cout << "Reference count: " << shared1.use_count() << std::endl;

{
// 复制 shared_ptr,引用计数增加
std::shared_ptr<Resource> shared2 = shared1;
std::cout << "Reference count: " << shared1.use_count() << std::endl;
shared2->doSomething();
} // shared2 离开作用域,引用计数减少

// 查看引用计数
std::cout << "Reference count after shared2 scope: " << shared1.use_count() << std::endl;
} // shared1 离开作用域,引用计数为 0,资源被删除
std::cout << "End of shared_ptr scope" << std::endl;

解析

  • std::shared_ptr:共享所有权的智能指针,使用引用计数管理资源
  • std::make_shared:创建 shared_ptr 的推荐方法,比直接使用 new 更高效
  • use_count():获取当前 shared_ptr 所管理资源的引用计数
  • 当引用计数为 0 时,资源会被自动删除

2.5 shared_ptr 与自定义删除器

1
2
3
4
5
6
7
8
9
10
11
std::cout << "\n=== shared_ptr 与自定义删除器 ===" << std::endl;
{
// 定义自定义删除器
auto customDeleter = [](Resource* r) {
std::cout << "Custom deleter called for " << r->getName() << std::endl;
delete r;
};
// 使用自定义删除器创建 shared_ptr
std::shared_ptr<Resource> customPtr(new Resource("Custom"), customDeleter);
customPtr->doSomething();
} // customPtr 离开作用域,调用自定义删除器

解析

  • 自定义删除器:可以为 shared_ptr 指定自定义的删除器,用于特殊的资源释放逻辑
  • 删除器可以是函数、函数对象或 lambda 表达式
  • shared_ptr 离开作用域且引用计数为 0 时,会调用自定义删除器

2.6 weak_ptr(弱引用智能指针)

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
std::cout << "\n=== weak_ptr(弱引用智能指针)===" << std::endl;
{
// 创建 shared_ptr
std::shared_ptr<Resource> shared = std::make_shared<Resource>("Weak");
// 创建 weak_ptr,不增加引用计数
std::weak_ptr<Resource> weak = shared;

// 查看引用计数(应该为 1,因为 weak_ptr 不增加引用计数)
std::cout << "Reference count: " << shared.use_count() << std::endl;

// 锁定 weak_ptr,获取 shared_ptr
if (auto locked = weak.lock()) {
locked->doSomething();
std::cout << "Weak pointer locked successfully" << std::endl;
} else {
std::cout << "Weak pointer is expired" << std::endl;
}

// 重置 shared_ptr,资源被删除
shared.reset();
std::cout << "After shared.reset()" << std::endl;

// 再次尝试锁定 weak_ptr
if (auto locked = weak.lock()) {
locked->doSomething();
} else {
std::cout << "Weak pointer is expired" << std::endl;
}
}

解析

  • std::weak_ptr:弱引用智能指针,不增加引用计数
  • lock():尝试获取一个 shared_ptr,如果资源仍然存在则返回有效的 shared_ptr,否则返回空 shared_ptr
  • 用于解决循环引用问题,以及需要检查资源是否仍然存在的场景

2.7 weak_ptr 打破循环引用

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
std::cout << "\n=== weak_ptr 打破循环引用 ===" << std::endl;
// 定义节点类
class Node {
public:
std::string name;
std::shared_ptr<Node> next; // 共享所有权
std::weak_ptr<Node> prev; // 弱引用,不增加引用计数

// 构造函数
Node(const std::string& n) : name(n) {
std::cout << "Node " << name << " created" << std::endl;
}

// 析构函数
~Node() {
std::cout << "Node " << name << " destroyed" << std::endl;
}
};

{
// 创建两个节点
auto node1 = std::make_shared<Node>("Node1");
auto node2 = std::make_shared<Node>("Node2");

// 建立双向链接
node1->next = node2;
node2->prev = node1;

// 查看引用计数
std::cout << "node1 use_count: " << node1.use_count() << std::endl;
std::cout << "node2 use_count: " << node2.use_count() << std::endl;
} // 离开作用域,两个节点都会被正确删除,因为使用了 weak_ptr 打破循环引用
std::cout << "End of node scope" << std::endl;

解析

  • 循环引用问题:当两个或多个 shared_ptr 相互引用时,会导致引用计数永远不为 0,从而导致内存泄漏
  • 解决方案:使用 weak_ptr 代替 shared_ptr 作为反向引用,这样就不会增加引用计数
  • 在上面的例子中,node1->nextshared_ptr,而 node2->prevweak_ptr,这样就打破了循环引用

2.8 shared_ptr 与 std::vector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::cout << "\n=== shared_ptr 与 std::vector ===" << std::endl;
{
// 创建存储 shared_ptr 的向量
std::vector<std::shared_ptr<Resource>> resources;
// 添加元素
resources.push_back(std::make_shared<Resource>("Resource1"));
resources.push_back(std::make_shared<Resource>("Resource2"));
resources.push_back(std::make_shared<Resource>("Resource3"));

// 遍历向量并使用元素
std::cout << "Resources in vector:" << std::endl;
for (const auto& res : resources) {
res->doSomething();
}
} // 离开作用域,向量中的所有 shared_ptr 被销毁,资源被删除
std::cout << "End of vector scope" << std::endl;

解析

  • std::vector 存储 shared_ptr 是一种常见的用法,用于管理多个资源
  • 当向量被销毁时,其中的所有 shared_ptr 也会被销毁,引用计数相应减少
  • 当引用计数为 0 时,资源会被自动删除

2.9 make_shared vs new

1
2
3
4
5
6
7
std::cout << "\n=== make_shared vs new(性能比较)===" << std::endl;
{
// 使用 make_shared 创建 shared_ptr(推荐)
auto ptr1 = std::make_shared<Resource>("MakeShared");
// 使用 new 创建 shared_ptr(不推荐)
std::shared_ptr<Resource> ptr2(new Resource("New"));
} // 离开作用域,两个资源都被删除

解析

  • std::make_shared 的优点:
    • 更简洁,代码可读性更好
    • 更高效,只进行一次内存分配(同时分配控制块和资源)
    • 更安全,避免了在创建 shared_ptr 之前发生异常导致的内存泄漏
  • 推荐使用 std::make_shared 而不是直接使用 new 创建 shared_ptr

2.10 智能指针与异常安全

1
2
3
4
5
6
7
8
9
10
11
12
13
std::cout << "\n=== 智能指针与异常安全 ===" << std::endl;
{
try {
// 创建智能指针
std::unique_ptr<Resource> ptr = std::make_unique<Resource>("ExceptionSafe");
// 模拟异常
throw std::runtime_error("Test exception");
// 即使发生异常,ptr 离开作用域时也会自动删除资源
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
std::cout << "Resource was properly deleted despite exception" << std::endl;
}
}

解析

  • 异常安全:即使在发生异常的情况下,智能指针也能确保资源被正确释放
  • 当异常发生时,局部变量会被自动销毁,智能指针的析构函数会被调用,从而释放资源
  • 相比之下,使用原始指针时,如果在 newdelete 之间发生异常,会导致内存泄漏

2.11 shared_ptr 的引用计数管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
std::cout << "\n=== shared_ptr 的引用计数管理 ===" << std::endl;
{
// 创建 shared_ptr
auto shared1 = std::make_shared<Resource>("RefCount");
std::cout << "After creation: " << shared1.use_count() << std::endl;

// 复制到另一个 shared_ptr
auto shared2 = shared1;
std::cout << "After copy: " << shared1.use_count() << std::endl;

// 复制到向量
std::vector<std::shared_ptr<Resource>> vec;
vec.push_back(shared1);
std::cout << "After push_back: " << shared1.use_count() << std::endl;

// 从向量中移除
vec.pop_back();
std::cout << "After pop_back: " << shared1.use_count() << std::endl;

// 重置 shared2
shared2.reset();
std::cout << "After shared2.reset(): " << shared1.use_count() << std::endl;
} // shared1 离开作用域,引用计数为 0,资源被删除

解析

  • 引用计数的变化:
    • 创建 shared_ptr 时,引用计数为 1
    • 复制 shared_ptr 时,引用计数增加
    • shared_ptr 离开作用域或被重置时,引用计数减少
    • 当引用计数为 0 时,资源被删除

2.12 智能指针的空检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
std::cout << "\n=== 智能指针的空检查 ===" << std::endl;
{
// 创建空的 unique_ptr
std::unique_ptr<Resource> emptyPtr;
// 检查是否为空
if (!emptyPtr) {
std::cout << "emptyPtr is null" << std::endl;
}

// 创建非空的 unique_ptr
std::unique_ptr<Resource> nonEmptyPtr = std::make_unique<Resource>("NotNull");
// 检查是否为空
if (nonEmptyPtr) {
std::cout << "nonEmptyPtr is not null" << std::endl;
nonEmptyPtr->doSomething();
}
}

解析

  • 智能指针可以像原始指针一样进行空检查
  • 空的智能指针转换为布尔值时为 false,非空的智能指针转换为布尔值时为 true

3. 编译和运行说明

3.1 编译命令

使用以下命令编译 smart_pointers.cpp 文件:

1
g++ -std=c++14 -fexec-charset=GBK -o smart_pointers smart_pointers.cpp

参数说明:

  • -std=c++14:使用 C++14 标准(因为使用了 std::make_unique,这是 C++14 引入的特性)
  • -fexec-charset=GBK:确保输出的中文字符正确显示
  • -o smart_pointers:指定输出可执行文件名为 smart_pointers

3.2 运行命令

编译成功后,使用以下命令运行程序:

1
2
./smart_pointers  # Linux/macOS
.\smart_pointers.exe # Windows

3.3 预期输出

运行程序后,您将看到类似以下的输出:

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
=== unique_ptr(独占所有权智能指针)===
Resource A created
Resource A is doing something
Resource name: A
ptr1 is null (moved to ptr2)
Resource A is doing something
Resource B created
Resource B destroyed
ptr3 reset
Resource A destroyed
End of unique_ptr scope

=== unique_ptr 与数组 ===
Array elements: 0 10 20 30 40

=== shared_ptr(共享所有权智能指针)===
Resource Shared created
Reference count: 1
Reference count: 2
Resource Shared is doing something
Reference count after shared2 scope: 1
Resource Shared destroyed
End of shared_ptr scope

=== shared_ptr 与自定义删除器 ===
Resource Custom created
Resource Custom is doing something
Custom deleter called for Custom
Resource Custom destroyed

=== weak_ptr(弱引用智能指针)===
Resource Weak created
Reference count: 1
Resource Weak is doing something
Weak pointer locked successfully
Resource Weak destroyed
After shared.reset()
Weak pointer is expired

=== weak_ptr 打破循环引用 ===
Node Node1 created
Node Node2 created
node1 use_count: 1
node2 use_count: 2
Node Node1 destroyed
Node Node2 destroyed
End of node scope

=== shared_ptr 与 std::vector ===
Resource Resource1 created
Resource Resource2 created
Resource Resource3 created
Resources in vector:
Resource Resource1 is doing something
Resource Resource2 is doing something
Resource Resource3 is doing something
Resource Resource1 destroyed
Resource Resource2 destroyed
Resource Resource3 destroyed
End of vector scope

=== make_shared vs new(性能比较)===
Resource MakeShared created
Resource New created
Resource New destroyed
Resource MakeShared destroyed

=== 智能指针与异常安全 ===
Resource ExceptionSafe created
Resource ExceptionSafe destroyed
Caught exception: Test exception
Resource was properly deleted despite exception

=== shared_ptr 的引用计数管理 ===
Resource RefCount created
After creation: 1
After copy: 2
After push_back: 3
After pop_back: 2
After shared2.reset(): 1
Resource RefCount destroyed

=== 智能指针的空检查 ===
emptyPtr is null
Resource NotNull created
nonEmptyPtr is not null
Resource NotNull is doing something
Resource NotNull destroyed

4. 技术要点

4.1 智能指针的类型和特性

智能指针类型 所有权 引用计数 线程安全 主要用途
unique_ptr 独占 管理不需要共享的资源
shared_ptr 共享 部分(引用计数操作是线程安全的) 管理需要共享的资源
weak_ptr 解决循环引用问题,观察资源是否存在

4.2 智能指针的使用场景

unique_ptr 的使用场景

  • 独占资源:当资源只需要被一个所有者管理时
  • 工厂函数返回值:工厂函数返回新创建的对象时
  • 作为成员变量:当类需要独占一个资源时
  • 容器元素:当容器需要存储对象的所有权时
  • 移动语义:当需要转移资源所有权时

shared_ptr 的使用场景

  • 共享资源:当资源需要被多个所有者共享时
  • 长期存在的对象:当对象的生命周期由多个部分共同管理时
  • 循环依赖:与 weak_ptr 配合使用,解决循环依赖问题
  • 多线程环境:当多个线程需要访问同一个对象时(需要注意对象本身的线程安全性)

weak_ptr 的使用场景

  • 打破循环引用:当存在 shared_ptr 循环引用时
  • 观察者模式:当需要观察一个对象但不希望影响其生命周期时
  • 缓存:当需要缓存对象但不希望阻止其被释放时
  • 延迟加载:当需要在需要时才获取对象时

4.3 智能指针的性能考虑

  • 内存开销

    • unique_ptr:最小,只需要存储一个指针
    • shared_ptr:较大,需要存储指针和引用计数(控制块)
    • weak_ptr:与 shared_ptr 类似,需要访问控制块
  • 时间开销

    • unique_ptr:几乎没有额外开销,与原始指针类似
    • shared_ptr
      • 创建:使用 make_shared 时开销较小
      • 复制:需要原子操作增加引用计数,有一定开销
      • 销毁:需要原子操作减少引用计数,有一定开销
    • weak_ptr
      • 锁定:需要检查资源是否存在,有一定开销
  • 内存分配

    • std::make_shared:只进行一次内存分配,同时分配控制块和资源
    • new + shared_ptr:进行两次内存分配,分别分配资源和控制块

4.4 智能指针的线程安全性

  • 引用计数的线程安全性

    • shared_ptr 的引用计数操作是线程安全的,多个线程可以同时增加或减少引用计数
    • weak_ptrlock() 操作也是线程安全的
  • 对象访问的线程安全性

    • 智能指针本身不保证对象访问的线程安全性
    • 如果多个线程同时访问智能指针管理的对象,需要额外的同步措施(如互斥锁)
  • 注意事项

    • 避免在多个线程之间共享 unique_ptr,因为它不支持并发访问
    • 对于 shared_ptr,虽然引用计数是线程安全的,但对象本身可能不是

4.5 智能指针的最佳实践

  • 优先使用 unique_ptr

    • 当资源只需要一个所有者时,优先使用 unique_ptr
    • unique_ptr 更轻量,语义更清晰
  • 合理使用 shared_ptr

    • 当资源需要多个所有者时,使用 shared_ptr
    • 优先使用 std::make_shared 创建 shared_ptr
    • 避免循环引用,使用 weak_ptr 打破循环引用
  • 正确使用 weak_ptr

    • 使用 weak_ptr 解决循环引用问题
    • 使用 lock() 方法安全地获取 shared_ptr
    • 检查 lock() 的返回值,确保资源仍然存在
  • 避免混合使用智能指针和原始指针

    • 尽量避免在智能指针和原始指针之间转换
    • 如果必须转换,确保原始指针的生命周期不超过智能指针
  • 避免使用智能指针管理非动态内存

    • 智能指针只应该用于管理动态分配的内存
    • 不要使用智能指针管理栈上的对象或全局对象

5. 常见问题解答

5.1 unique_ptr 和 shared_ptr 有什么区别?

解答

  • 所有权unique_ptr 独占所有权,同一时间只能有一个 unique_ptr 管理资源;shared_ptr 共享所有权,多个 shared_ptr 可以管理同一个资源。
  • 引用计数unique_ptr 不使用引用计数,shared_ptr 使用引用计数。
  • 性能unique_ptr 更轻量,几乎没有额外开销;shared_ptr 有一定的内存和时间开销。
  • 线程安全shared_ptr 的引用计数操作是线程安全的,unique_ptr 不是。
  • 使用场景unique_ptr 适用于资源只需要一个所有者的场景;shared_ptr 适用于资源需要多个所有者的场景。

5.2 如何解决 shared_ptr 的循环引用问题?

解答

  • 使用 weak_ptr 打破循环引用。当两个或多个 shared_ptr 相互引用时,将其中一个引用改为 weak_ptr,这样就不会增加引用计数。
  • 例如,在双向链表中,前向指针使用 shared_ptr,反向指针使用 weak_ptr

5.3 make_shared 和 new 有什么区别?

解答

  • 内存分配std::make_shared 只进行一次内存分配,同时分配控制块和资源;new + shared_ptr 进行两次内存分配,分别分配资源和控制块。
  • 异常安全std::make_shared 更安全,避免了在创建 shared_ptr 之前发生异常导致的内存泄漏。
  • 代码可读性std::make_shared 更简洁,代码可读性更好。
  • 内存使用std::make_shared 创建的对象和控制块在同一块内存中,可能会导致对象的内存比实际需要的时间更长(因为控制块可能被 weak_ptr 引用)。

5.4 智能指针如何与异常处理交互?

解答

  • 智能指针提供了异常安全的内存管理。即使在发生异常的情况下,智能指针也能确保资源被正确释放。
  • 当异常发生时,局部变量会被自动销毁,智能指针的析构函数会被调用,从而释放资源。
  • 相比之下,使用原始指针时,如果在 newdelete 之间发生异常,会导致内存泄漏。

5.5 如何在智能指针和原始指针之间转换?

解答

  • 从智能指针获取原始指针
    • unique_ptr:使用 get() 方法
    • shared_ptr:使用 get() 方法
  • 从原始指针创建智能指针
    • unique_ptrstd::unique_ptr<T> ptr(new T)std::make_unique<T>()
    • shared_ptrstd::shared_ptr<T> ptr(new T)std::make_shared<T>()
  • 注意事项
    • 避免将同一个原始指针同时用于多个智能指针
    • 避免使用智能指针管理由其他方式分配的内存
    • 确保原始指针的生命周期不超过智能指针

5.6 智能指针可以管理数组吗?

解答

  • unique_ptr:可以管理数组,使用 std::unique_ptr<T[]> 特化版本,会自动使用 delete[] 删除数组。
  • shared_ptr:可以管理数组,但需要指定自定义删除器,因为默认情况下会使用 delete 而不是 delete[]
    • 例如:std::shared_ptr<int[]> arr(new int[5], std::default_delete<int[]>());
    • 或者使用 std::make_shared 配合 std::unique_ptrstd::shared_ptr<int[]> arr(std::make_unique<int[]>(5).release());

5.7 智能指针的控制块是什么?

解答

  • 控制块是 shared_ptrweak_ptr 用于管理资源的内部数据结构,包含以下信息:
    • 引用计数(被 shared_ptr 引用的次数)
    • 弱引用计数(被 weak_ptr 引用的次数)
    • 自定义删除器(如果有)
    • 自定义分配器(如果有)
  • 控制块的内存分配:
    • 使用 std::make_shared 时,控制块和资源在同一块内存中分配
    • 使用 new + shared_ptr 时,控制块和资源在不同的内存块中分配
  • 弱引用计数:当弱引用计数为 0 时,控制块会被销毁

6. 代码优化建议

6.1 智能指针的选择

  • 优先使用 unique_ptr

    • 当资源只需要一个所有者时,优先使用 unique_ptr
    • unique_ptr 更轻量,语义更清晰
    • 示例:
      1
      2
      3
      4
      5
      // 推荐
      std::unique_ptr<Resource> ptr = std::make_unique<Resource>("A");

      // 不推荐
      std::shared_ptr<Resource> ptr = std::make_shared<Resource>("A");
  • 合理使用 shared_ptr

    • 当资源需要多个所有者时,使用 shared_ptr
    • 示例:
      1
      2
      3
      // 当需要共享所有权时
      std::shared_ptr<Resource> ptr1 = std::make_shared<Resource>("Shared");
      std::shared_ptr<Resource> ptr2 = ptr1; // 共享所有权

6.2 智能指针的创建

  • 使用 make_shared 和 make_unique
    • 优先使用 std::make_sharedstd::make_unique 创建智能指针
    • 更简洁,更高效,更安全
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      // 推荐
      auto ptr1 = std::make_shared<Resource>("A");
      auto ptr2 = std::make_unique<Resource>("B");

      // 不推荐
      std::shared_ptr<Resource> ptr1(new Resource("A"));
      std::unique_ptr<Resource> ptr2(new Resource("B"));

6.3 智能指针的使用

  • 避免不必要的复制

    • 对于 shared_ptr,避免不必要的复制,因为这会增加引用计数的开销
    • 当只需要访问对象而不需要共享所有权时,使用引用或指针
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      // 推荐:使用引用
      void processResource(const Resource& res) {
      res.doSomething();
      }

      // 调用
      std::shared_ptr<Resource> ptr = std::make_shared<Resource>("A");
      processResource(*ptr);

      // 不推荐:不必要的复制
      void processResource(std::shared_ptr<Resource> res) {
      res->doSomething();
      }
  • 使用移动语义

    • 对于 unique_ptr,当需要转移所有权时,使用 std::move
    • 示例:
      1
      2
      std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>("A");
      std::unique_ptr<Resource> ptr2 = std::move(ptr1); // 转移所有权

6.4 智能指针与容器

  • 在容器中存储智能指针
    • 当容器需要管理对象的生命周期时,存储智能指针
    • 对于 unique_ptr,需要使用移动语义或 C++11 后的容器
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      // 存储 unique_ptr
      std::vector<std::unique_ptr<Resource>> resources;
      resources.push_back(std::make_unique<Resource>("A"));

      // 存储 shared_ptr
      std::vector<std::shared_ptr<Resource>> resources;
      resources.push_back(std::make_shared<Resource>("A"));

6.5 智能指针与继承

  • 使用智能指针管理多态对象
    • 智能指针可以很好地管理多态对象
    • 确保基类有虚析构函数
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      class Base {
      public:
      virtual ~Base() = default;
      virtual void doSomething() = 0;
      };

      class Derived : public Base {
      public:
      void doSomething() override {
      std::cout << "Derived::doSomething()" << std::endl;
      }
      };

      // 使用智能指针管理多态对象
      std::unique_ptr<Base> ptr = std::make_unique<Derived>();
      ptr->doSomething(); // 多态调用

6.6 智能指针与异常处理

  • 利用智能指针的异常安全性
    • 当在函数中分配资源并可能抛出异常时,使用智能指针
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      // 推荐:使用智能指针
      void function() {
      auto ptr = std::make_unique<Resource>("A");
      // 可能抛出异常的代码
      throw std::runtime_error("Error");
      // 即使抛出异常,ptr 也会自动释放资源
      }

      // 不推荐:使用原始指针
      void function() {
      Resource* ptr = new Resource("A");
      // 可能抛出异常的代码
      throw std::runtime_error("Error");
      // 抛出异常后,delete 不会被执行,导致内存泄漏
      delete ptr;
      }

6.7 智能指针的自定义删除器

  • 使用 lambda 表达式作为自定义删除器
    • 当需要特殊的资源释放逻辑时,使用自定义删除器
    • lambda 表达式是定义自定义删除器的便捷方式
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 自定义删除器
      auto deleter = [](Resource* r) {
      std::cout << "Custom deleter" << std::endl;
      delete r;
      };

      // 使用自定义删除器
      std::unique_ptr<Resource, decltype(deleter)> ptr(new Resource("A"), deleter);
      std::shared_ptr<Resource> ptr2(new Resource("B"), deleter);

7. 代码优化示例

7.1 从原始指针到智能指针的转换

优化前

1
2
3
4
5
6
7
8
9
10
11
void process() {
Resource* ptr = new Resource("A");
try {
ptr->doSomething();
// 可能抛出异常的代码
} catch (const std::exception& e) {
delete ptr; // 需要手动释放资源
throw;
}
delete ptr; // 需要手动释放资源
}

优化后

1
2
3
4
5
6
void process() {
auto ptr = std::make_unique<Resource>("A");
ptr->doSomething();
// 可能抛出异常的代码
// 不需要手动释放资源,ptr 离开作用域时会自动释放
}

7.2 解决循环引用问题

优化前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Node {
public:
std::string name;
std::shared_ptr<Node> next;
std::shared_ptr<Node> prev; // 循环引用

Node(const std::string& n) : name(n) {}
~Node() { std::cout << "Node " << name << " destroyed" << std::endl; }
};

void process() {
auto node1 = std::make_shared<Node>("Node1");
auto node2 = std::make_shared<Node>("Node2");

node1->next = node2;
node2->prev = node1; // 循环引用,导致内存泄漏

std::cout << "node1 use_count: " << node1.use_count() << std::endl;
std::cout << "node2 use_count: " << node2.use_count() << std::endl;
} // 离开作用域后,node1 和 node2 的引用计数仍然为 1,导致内存泄漏

优化后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Node {
public:
std::string name;
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 使用 weak_ptr 打破循环引用

Node(const std::string& n) : name(n) {}
~Node() { std::cout << "Node " << name << " destroyed" << std::endl; }
};

void process() {
auto node1 = std::make_shared<Node>("Node1");
auto node2 = std::make_shared<Node>("Node2");

node1->next = node2;
node2->prev = node1; // 不再是循环引用

std::cout << "node1 use_count: " << node1.use_count() << std::endl;
std::cout << "node2 use_count: " << node2.use_count() << std::endl;
} // 离开作用域后,node1 和 node2 的引用计数为 0,资源被正确释放

7.3 智能指针与工厂模式

优化前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Product {
public:
virtual ~Product() = default;
virtual void use() = 0;
};

class ConcreteProduct : public Product {
public:
void use() override { std::cout << "Using ConcreteProduct" << std::endl; }
};

Product* createProduct() {
return new ConcreteProduct();
}

void process() {
Product* product = createProduct();
product->use();
delete product; // 需要手动释放资源
}

优化后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Product {
public:
virtual ~Product() = default;
virtual void use() = 0;
};

class ConcreteProduct : public Product {
public:
void use() override { std::cout << "Using ConcreteProduct" << std::endl; }
};

std::unique_ptr<Product> createProduct() {
return std::make_unique<ConcreteProduct>();
}

void process() {
auto product = createProduct();
product->use();
// 不需要手动释放资源,product 离开作用域时会自动释放
}

7.4 智能指针与容器

优化前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void process() {
std::vector<Resource*> resources;
for (int i = 0; i < 5; ++i) {
resources.push_back(new Resource(std::to_string(i)));
}

for (auto* res : resources) {
res->doSomething();
}

for (auto* res : resources) {
delete res; // 需要手动释放资源
}
}

优化后

1
2
3
4
5
6
7
8
9
10
11
void process() {
std::vector<std::unique_ptr<Resource>> resources;
for (int i = 0; i < 5; ++i) {
resources.push_back(std::make_unique<Resource>(std::to_string(i)));
}

for (const auto& res : resources) {
res->doSomething();
}
// 不需要手动释放资源,vector 销毁时会自动释放所有资源
}

7.5 智能指针与多线程

优化前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void threadFunction(Resource* res) {
// 处理资源
res->doSomething();
}

void process() {
Resource* res = new Resource("A");

std::thread t1(threadFunction, res);
std::thread t2(threadFunction, res);

t1.join();
t2.join();

delete res; // 需要手动释放资源
}

优化后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void threadFunction(std::shared_ptr<Resource> res) {
// 处理资源
res->doSomething();
}

void process() {
auto res = std::make_shared<Resource>("A");

std::thread t1(threadFunction, res);
std::thread t2(threadFunction, res);

t1.join();
t2.join();
// 不需要手动释放资源,所有线程结束后会自动释放
}

8. 总结

C++ 智能指针是现代 C++ 中管理动态内存的重要工具,它们能够自动管理资源的生命周期,避免内存泄漏,提高代码的安全性和可靠性。本指南详细介绍了三种主要的智能指针:

  1. unique_ptr:独占所有权的智能指针,适用于资源只需要一个所有者的场景,是最轻量的智能指针。

  2. shared_ptr:共享所有权的智能指针,使用引用计数管理资源,适用于资源需要多个所有者的场景。

  3. weak_ptr:弱引用智能指针,不增加引用计数,主要用于解决循环引用问题,以及观察资源是否存在。

8.1 核心要点

  • RAII 原则:智能指针基于 RAII(资源获取即初始化)原则,利用对象的生命周期管理资源。
  • 自动内存管理:智能指针能够自动释放所管理的资源,避免内存泄漏。
  • 异常安全:即使在发生异常的情况下,智能指针也能确保资源被正确释放。
  • 所有权管理:智能指针明确表达了资源的所有权关系,使代码更易于理解和维护。
  • 性能考虑:不同的智能指针有不同的性能特点,应根据具体场景选择合适的智能指针。

8.2 最佳实践

  • 优先使用 unique_ptr:当资源只需要一个所有者时,优先使用 unique_ptr
  • 合理使用 shared_ptr:当资源需要多个所有者时,使用 shared_ptr
  • 使用 make_shared 和 make_unique:优先使用 std::make_sharedstd::make_unique 创建智能指针。
  • 避免循环引用:使用 weak_ptr 打破 shared_ptr 的循环引用。
  • 注意异常安全:利用智能指针的异常安全性,避免手动管理内存导致的内存泄漏。
  • 考虑性能:根据具体场景选择合适的智能指针,平衡安全性和性能。

8.3 应用场景

  • 资源管理:管理动态分配的内存、文件句柄、网络连接等资源。
  • 工厂模式:工厂函数返回智能指针,确保资源被正确释放。
  • 容器:在容器中存储智能指针,管理对象的生命周期。
  • 多线程:在多线程环境中安全地共享资源。
  • 观察者模式:使用 weak_ptr 实现观察者模式,避免内存泄漏。

通过掌握智能指针的使用方法和最佳实践,您可以编写更安全、更可靠、更易于维护的 C++ 代码,避免内存泄漏等常见问题,提高代码质量和开发效率。

smart_pointers.cpp

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <functional>

// 资源类,用于演示智能指针的使用
class Resource {
private:
std::string name;

public:
// 构造函数
Resource(const std::string& n) : name(n) {
std::cout << "Resource " << name << " created" << std::endl;
}

// 析构函数
~Resource() {
std::cout << "Resource " << name << " destroyed" << std::endl;
}

// 成员函数
void doSomething() const {
std::cout << "Resource " << name << " is doing something" << std::endl;
}

// 获取资源名称
std::string getName() const {
return name;
}
};

int main() {
std::cout << "=== unique_ptr(独占所有权智能指针)===" << std::endl;
{
// 创建 unique_ptr
std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>("A");
// 使用箭头运算符访问成员
ptr1->doSomething();
std::cout << "Resource name: " << ptr1->getName() << std::endl;

// 移动所有权
std::unique_ptr<Resource> ptr2 = std::move(ptr1);
// 检查 ptr1 是否为空
if (ptr1) {
std::cout << "ptr1 is valid" << std::endl;
} else {
std::cout << "ptr1 is null (moved to ptr2)" << std::endl;
}

// 检查 ptr2 是否有效
if (ptr2) {
ptr2->doSomething();
}

// 创建另一个 unique_ptr
std::unique_ptr<Resource> ptr3 = std::make_unique<Resource>("B");
// 重置 unique_ptr,会自动删除所管理的资源
ptr3.reset();
std::cout << "ptr3 reset" << std::endl;
}
std::cout << "End of unique_ptr scope" << std::endl;

std::cout << "\n=== unique_ptr 与数组 ===" << std::endl;
{
// 创建管理数组的 unique_ptr
std::unique_ptr<int[]> arr = std::make_unique<int[]>(5);
// 初始化数组元素
for (int i = 0; i < 5; ++i) {
arr[i] = i * 10;
}
// 打印数组元素
std::cout << "Array elements: ";
for (int i = 0; i < 5; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// 离开作用域时,unique_ptr 会自动删除数组
}

std::cout << "\n=== shared_ptr(共享所有权智能指针)===" << std::endl;
{
// 创建 shared_ptr
std::shared_ptr<Resource> shared1 = std::make_shared<Resource>("Shared");
// 查看引用计数
std::cout << "Reference count: " << shared1.use_count() << std::endl;

{
// 复制 shared_ptr,引用计数增加
std::shared_ptr<Resource> shared2 = shared1;
std::cout << "Reference count: " << shared1.use_count() << std::endl;
shared2->doSomething();
} // shared2 离开作用域,引用计数减少

// 查看引用计数
std::cout << "Reference count after shared2 scope: " << shared1.use_count() << std::endl;
} // shared1 离开作用域,引用计数为 0,资源被删除
std::cout << "End of shared_ptr scope" << std::endl;

std::cout << "\n=== shared_ptr 与自定义删除器 ===" << std::endl;
{
// 定义自定义删除器
auto customDeleter = [](Resource* r) {
std::cout << "Custom deleter called for " << r->getName() << std::endl;
delete r;
};
// 使用自定义删除器创建 shared_ptr
std::shared_ptr<Resource> customPtr(new Resource("Custom"), customDeleter);
customPtr->doSomething();
} // customPtr 离开作用域,调用自定义删除器

std::cout << "\n=== weak_ptr(弱引用智能指针)===" << std::endl;
{
// 创建 shared_ptr
std::shared_ptr<Resource> shared = std::make_shared<Resource>("Weak");
// 创建 weak_ptr,不增加引用计数
std::weak_ptr<Resource> weak = shared;

// 查看引用计数(应该为 1,因为 weak_ptr 不增加引用计数)
std::cout << "Reference count: " << shared.use_count() << std::endl;

// 锁定 weak_ptr,获取 shared_ptr
if (auto locked = weak.lock()) {
locked->doSomething();
std::cout << "Weak pointer locked successfully" << std::endl;
} else {
std::cout << "Weak pointer is expired" << std::endl;
}

// 重置 shared_ptr,资源被删除
shared.reset();
std::cout << "After shared.reset()" << std::endl;

// 再次尝试锁定 weak_ptr
if (auto locked = weak.lock()) {
locked->doSomething();
} else {
std::cout << "Weak pointer is expired" << std::endl;
}
}

std::cout << "\n=== weak_ptr 打破循环引用 ===" << std::endl;
// 定义节点类
class Node {
public:
std::string name;
std::shared_ptr<Node> next; // 共享所有权
std::weak_ptr<Node> prev; // 弱引用,不增加引用计数

// 构造函数
Node(const std::string& n) : name(n) {
std::cout << "Node " << name << " created" << std::endl;
}

// 析构函数
~Node() {
std::cout << "Node " << name << " destroyed" << std::endl;
}
};

{
// 创建两个节点
auto node1 = std::make_shared<Node>("Node1");
auto node2 = std::make_shared<Node>("Node2");

// 建立双向链接
node1->next = node2;
node2->prev = node1;

// 查看引用计数
std::cout << "node1 use_count: " << node1.use_count() << std::endl;
std::cout << "node2 use_count: " << node2.use_count() << std::endl;
} // 离开作用域,两个节点都会被正确删除,因为使用了 weak_ptr 打破循环引用
std::cout << "End of node scope" << std::endl;

std::cout << "\n=== shared_ptr 与 std::vector ===" << std::endl;
{
// 创建存储 shared_ptr 的向量
std::vector<std::shared_ptr<Resource>> resources;
// 添加元素
resources.push_back(std::make_shared<Resource>("Resource1"));
resources.push_back(std::make_shared<Resource>("Resource2"));
resources.push_back(std::make_shared<Resource>("Resource3"));

// 遍历向量并使用元素
std::cout << "Resources in vector:" << std::endl;
for (const auto& res : resources) {
res->doSomething();
}
} // 离开作用域,向量中的所有 shared_ptr 被销毁,资源被删除
std::cout << "End of vector scope" << std::endl;

std::cout << "\n=== make_shared vs new(性能比较)===" << std::endl;
{
// 使用 make_shared 创建 shared_ptr(推荐)
auto ptr1 = std::make_shared<Resource>("MakeShared");
// 使用 new 创建 shared_ptr(不推荐)
std::shared_ptr<Resource> ptr2(new Resource("New"));
} // 离开作用域,两个资源都被删除

std::cout << "\n=== 智能指针与异常安全 ===" << std::endl;
{
try {
// 创建智能指针
std::unique_ptr<Resource> ptr = std::make_unique<Resource>("ExceptionSafe");
// 模拟异常
throw std::runtime_error("Test exception");
// 即使发生异常,ptr 离开作用域时也会自动删除资源
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
std::cout << "Resource was properly deleted despite exception" << std::endl;
}
}

std::cout << "\n=== shared_ptr 的引用计数管理 ===" << std::endl;
{
// 创建 shared_ptr
auto shared1 = std::make_shared<Resource>("RefCount");
std::cout << "After creation: " << shared1.use_count() << std::endl;

// 复制到另一个 shared_ptr
auto shared2 = shared1;
std::cout << "After copy: " << shared1.use_count() << std::endl;

// 复制到向量
std::vector<std::shared_ptr<Resource>> vec;
vec.push_back(shared1);
std::cout << "After push_back: " << shared1.use_count() << std::endl;

// 从向量中移除
vec.pop_back();
std::cout << "After pop_back: " << shared1.use_count() << std::endl;

// 重置 shared2
shared2.reset();
std::cout << "After shared2.reset(): " << shared1.use_count() << std::endl;
} // shared1 离开作用域,引用计数为 0,资源被删除

std::cout << "\n=== 智能指针的空检查 ===" << std::endl;
{
// 创建空的 unique_ptr
std::unique_ptr<Resource> emptyPtr;
// 检查是否为空
if (!emptyPtr) {
std::cout << "emptyPtr is null" << std::endl;
}

// 创建非空的 unique_ptr
std::unique_ptr<Resource> nonEmptyPtr = std::make_unique<Resource>("NotNull");
// 检查是否为空
if (nonEmptyPtr) {
std::cout << "nonEmptyPtr is not null" << std::endl;
nonEmptyPtr->doSomething();
}
}

return 0;
}