再谈 shared_ptr
2025-06-28 10:03:11

原理

shared_ptr在底层实现上,维护一个引用计数,来管理内存对象的生命周期:当新构造一个对象时,引用计数初始化为1,拷贝对象时,引用计数加1,对象作用域结束析构时,引用计数减1,当最后一个对象被销毁时,引用计数会减为0,所持有的资源被释放!

线程安全性

shared_ptr保证多个线程能够安全地增加或减少其引用计数。但如果多个线程同时读写同一个shared_ptr或操作其管理的对象,那么需要额外进行同步机制,比如使用互斥锁mutex来保护这些操作。

手写一个shared_ptr

shared_ptr内部包含两部分指针,一个指向实际管理的资源,另一个指向控制块。控制块中包含引用计数,当shared_ptr被拷贝的时候,引用计数增加,销毁的时候引用计数减少,当引用计数变为0的时候,资源被释放!

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
// 管理引用计数的类
class SharedCount
{
public:
SharedCount() : count_{1} {};

void add() { ++count_; }

void minus() { --count_; }

int get() const { return count_; }
private:
std::atomic<int> count_;
};

// 智能指针类
template<typename T>
class SharedPtr
{
public:
SharedPtr(T* ptr) : ptr_{ptr}, ref_count_{new SharedCount} {}

SharedPtr() : ptr_{nullptr}, ref_count_{new SharedCount} {}

// 拷贝构造函数
SharedPtr(const SharedPtr& p)
{
this->ptr_ = p.ptr_;
this->ref_count_ = p.ref_count_;
ref_count_->add();
}

// 拷贝赋值函数
SharedPtr& operator=(const SharedPtr& p)
{
if (&p == this) return *this;
clean();
this->ptr_ = p.ptr_;
this->ref_count_ = p.ref_count_;
ref_count_->add();
return *this;
}

// 移动语义中,引用计数不变,同时清空原参数中的指针!
// 移动构造函数
SharedPtr(SharedPtr&& p)
{
this->ptr_ = p.ptr_;
this->ref_count_ = p.ref_count_;
p.ptr_ = nullptr;
p.ref_count_ = nullptr;
}

// 移动赋值函数
SharedPtr& operator=(SharedPtr && p)
{
if (&p == this) return *this;
clean();
this->ptr_ = p.ptr_;
this->ref_count_ = p.ref_count_;
p.ptr_ = nullptr;
p.ref_count_ = nullptr;
return *this;
}

~SharedPtr { clean(); }
private:
T* ptr_;
SharedCount* ref_count_;

// 引用计数减1,若最终引用计数减至0,则释放指针
void clean()
{
if (ref_count_)
{
ref_count_->minus();
if (ref_count_->get() == 0)
{
if (ptr_)
delete ptr_;
delete ref_count_;
}
}
}
};

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
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
#include <memory>

using namespace std;

class B; // 前置声明
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
shared_ptr<B> _ptrb;
};

class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
shared_ptr<A> _ptra;
};

int main()
{
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());

pa->_ptrb = pb;
pb->_ptra = pa;

cout << pa.use_count() << endl;
cout << pb.use_count() << endl;

return 0;
}
  • 以上代码输出A() B() 2 2,显然强智能指针管理的资源未能释放,这是因为程序结束时,强智能指针管理的资源计数-1不为0,因此无法释放。

weak_ptr解决交叉引用问题

shared_ptr交叉引用会导致new的资源无法释放,从而造成资源泄露问题!→ 注意到weak_ptr并不改变资源的引用计数,相当于”观察者“,故可以用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
34
35
#include <iostream>
#include <memory>

using namespace std;

class B;
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
weak_ptr<B> _ptrb; // 弱智能指针不会改变资源的引用计数
};

class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
weak_ptr<A> _ptra; // 弱智能指针不会改变资源的引用计数
};

int main()
{
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());

pa->_ptrb = pb;
pb->_ptra = pa;

cout << pa.use_count() << endl;
cout << pb.use_count() << endl;

return 0;
}

weak_ptr调用其它类中的方法

实际场景中,利用弱智能指针的特性解决了资源泄露问题,但是类B想通过智能指针调用类A中的一个方法,并不能直接调用,因为弱智能指针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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <iostream>
#include <memory>

using namespace std;

class B;
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void testA() { cout << "testA()" << endl; }
weak_ptr<B> _ptrb;
};

class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
void func()
{
// _ptra->testA(); // 弱智能指针无法直接使用资源
shared_ptr<A> ps = _ptra.lock(); // 提升方法
if (ps != nullptr) // 提升成功!
{
ps->testA();
}
}
weak_ptr<A> _ptra;
};

int main()
{
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());

pa->_ptrb = pb;
pb->_ptra = pa;

pb->func();

cout << pa.use_count() << endl;
cout << pb.use_count() << endl;

return 0;
}
Prev
2025-06-28 10:03:11
Next