谢尔德指针

shared_ptr

1. 简介

前面一篇文章已经介绍过智能指针 unique_ptr, 本节介绍 shared_ptr.

std::shared_ptr 通过一个指针, 持有一个共享对象的所有权. C++ 允许多个 shared_ptr 指向同一个对象. 当出现如下情形时, shared_ptr 指向的对象会被销毁且其占用的内存空间会被回收:

  1. 最后一个占有对象的指针 shared_ptr 被销毁;

  2. 最后一个占有对象的指针 shared_ptr 通过重载运算 operator= 赋值给其他指针, 或者调用函数 reset()时.

上面的对象销毁可以使用删除器表达式, 或者是在构造期间提供给 shared_ptr 的一个自定义删除器.

shared_ptr 能够在存储指向一个对象的指针时, 共享另一个对象的所有权. 这一特性可以用来在占有其所属对象时, 指向成员对象. 所存储的指针可以被函数 get()、解引用以及比较运算符访问. 当引用计数为 0 时, 被管理的指针会被传递给删除器.

unique_ptr 类似, shared_ptr 也可以不占有任何对象, 这种情况下即为 empty. 若一个空 shared_ptr 是通过别名构造器创建的, 那么它就可能拥有非空的存储指针.

在不附加同步的情况下, 多个线程能在 shared_ptr 的不同实例上调用所有的成员函数, 包括拷贝构造函数和拷贝赋值函数, 即使这些实例是副本, 且共享同一个对象的所有权. 如果多个线程的执行, 访问同一个 shared_ptr 的实例而不进行同步, 并且任一线程调用 shared_ptr 的非 const 成员函数, 那么就会出现数据竞争; shared_ptr 中原子函数的重载可以避免数据竞争的发生.

优雅使用 shared_ptr 的注意事项:

另外一个 shared_ptr 指针要想共享一个对象的所有权, 只能通过拷贝构造或者拷贝赋值的方式将其值赋给这个 shared_ptr 指针. 换句话说, 用另一个 shared_ptr 所占有的原始指针创建新的 shared_ptr 将会导致未定义行为.

当然, std::shared_ptr 允许不完整类型 T. 但是, 来自原始指针的构造器 template<class Y> shared_ptr(Y*) 与 成员函数 template<class Y> void reset(Y*) 只能被指向完整类型的指针调用, 应当注意的是, std::shared_ptr 可以通过一个原始指针构造成不完整类型.

std::shared_ptr 中的 T 可以是函数类型: 也就是说, 这种情况下, T 是一个函数指针, 而不是简单的对象指针. 这种情况可以用来保有一个动态库或者作为一个插件进行装载, 只要任意函数被引用了:

1
2
3
4
5
6
void del(void(*)()) {}
void fun() {}
int main(){
std::shared_ptr<void()> ee(fun, del);
    (*ee)();
}

在如下典型的实现方式中, std::shared_ptr 只持有两个指针:

  • get() 返回的指针;

  • 指向 控制块 的指针.

所谓的控制块是一种动态分配的对象:

  • 要么是指向所属对象的指针, 要么是所属对象自身;

  • 无关类型的删除器;

  • 无关类型的分配器;

  • 占有被管理对象的 shared_ptr 指针的数量;

  • 引用被管理对象的 weak_ptr 指针的数量.

当通过 std::make_shared 或者 std::allocate_shared 方式创建 shared_ptr 对象时, 控制块和被管理对象二者的内存空间都是一次分配的. 被管理对象是在控制块的数据成员中原位构造.

如果 shared_ptr 是通过某个构造函数进行构造的, 那被管理的对象和控制块必须分别进行内存空间的分配.这种情况下, 控制块存有一个指向被管理对象的指针.

shared_ptr 所持有的指针可以通过 get() 直接返回, 而由控制块管理的指针或者对象则是引用计数为0时被释放的那个. 因此, 二者并不一定等价.

shared_ptr 的析构函数将控制块的引用计数减一. 如果引用计数减为0, 控制块将会调用被管理对象的析构函数. 但控制块直到 std::weak_ptr 的引用计数为零时, 才会被释放自身.

在现存实现方式中, 如果有一个共享指针指向同一个控制块, 那弱指针数会自增一.

同时, 为了满足线程安全的要求, 引用计数会等价地使用 std::memory_order_relaxed 中的 std::atomic::fetch_add 自增; 如果自减, 则要求严格的顺序, 安全地销毁控制块.

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include<iostream>
#include<memory>
#include<thread>
#include<chrono>
#include<mutex>

struct Base
{
Base()
{
std::cout << " Base::Base()\n";
}
~Base()
{
std::cout << " Base::~Base()\n";
}
};

struct Derived: public Base
{
Derived()
{
std::cout << " Derived::Derived()\n" << '\n';
}
~Derived()
{
std::cout << "\n";
std::cout << " Derived::~Derived()\n";
}
};

void thr(std::shared_ptr<Base> p)
{
std::this_thread::sleep_for(std::chrono::seconds(1));

std::shared_ptr<Base> lp = p;

{
static std::mutex io_mutex;
std::lock_guard<std::mutex> lk(io_mutex);
std::cout << "local pointer in a thread:\n"
<< " lp.get() = " << lp.get()
<< ", lp.use_count() = " << lp.use_count() << '\n';
}
}

void copy(std::shared_ptr<Base> p)
{
std::cout << "==================================" << '\n';
std::cout << "call copy" << '\n';
std::shared_ptr<Base> lp = p;
std::cout << "current p use_count = " << p.use_count() << '\n';
std::cout << "exist copy" << '\n';
std::cout << "==================================" << '\n';
}

int main()
{
std::shared_ptr<Base> p = std::make_shared<Base>();

std::cout << "Created a shared Derived (as a pointer to Base)\n"
<< " p.get() = " << p.get()
<< ", p.use_count() = " << p.use_count() << '\n';
std::thread t1(thr, p), t2(thr, p), t3(thr, p);


copy(p);

std::cout << "current p.use_count() = " << p.use_count() << '\n';

p.reset();

std::cout << '\n';

std::cout << "Shared ownership between 3 threads and released\n"
<< "ownership from main:\n"
<< " p.get() = " << p.get()
<< ", p.use_count() = " << p.use_count() << '\n';

t1.join(); t2.join(); t3.join();
std::cout << "All threads completed, the last one deleted Derived\n";
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Base::Base()
Created a shared Derived (as a pointer to Base)
p.get() = 0x80003a040, p.use_count() = 1
==================================
call copy
current p use_count = 6
exist copy
==================================
current p.use_count() = 4

Shared ownership between 3 threads and released
ownership from main:
p.get() = 0, p.use_count() = 0
local pointer in a thread:
lp.get() = 0x80003a040, lp.use_count() = 6
local pointer in a thread:
lp.get() = 0x80003a040, lp.use_count() = 4
local pointer in a thread:
lp.get() = 0x80003a040, lp.use_count() = 2
Base::~Base()
All threads completed, the last one deleted Derived

谢尔德指针
https://socod.github.io/2022/07/d7e9fc032ed0/
作者
socod
发布于
2022年7月17日
许可协议