C++ 智能指针总结

根据 cppreference,C++ 提供了这些智能指针,以及和它们有联系的技术(未标注的均为 since c++11)

智能指针:

  • unique_ptr
  • shared_ptr
  • weak_ptr

相关技术:

  • owner_less

  • enable_shared_from_this

  • bad_weak_ptr

  • default_delete

  • out_ptr_t(c++23)

  • inout_ptr_t(c++23)

智能指针

所有智能指针默认非线程安全

通常 unique_ptr 和 raw_ptr 组合使用;shared_ptr 和 weak_ptr 组合使用;

unique_ptr 独占资源所属权,它其实可以取代 scope pointer,很好的贯彻了 RAII 思想,让空悬指针、野指针、double free、memory leak 等内存问题得到轻松的解决;

shared_ptr 共享资源所属权,控制了对象的生命周期,当没有任何 shared_ptr 指向资源时,资源会被自动释放,这能帮助我们实现 GC,这里的 Garbage 不光指 memory,还包括任何系统资源;在多线程场景下,它能帮我们安全析构对象;

weak_ptr 不会对对象的生命周期产生影响,也不持有资源的所有权,但是它能够知道某个对象是否还“活着”;在必要时,它还能提升为 shared_ptr;通常把它和 shared_ptr 组合起来用于双向关联的两个类(见 muduo 阅读笔记);

我根据三个智能指针的功能,写了简单的实现:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
template <class T>
class unique_ptr {
public:
explicit unique_ptr(T* raw_ptr)
: raw_ptr_(raw_ptr)
{}

// move
unique_ptr(unique_ptr<T>&& rhs) {
raw_ptr_ = rhs.raw_ptr_;
rhs.raw_ptr_ = nullptr;
}
unique_ptr& operator=(unique_ptr<T>&& rhs) {
this->~unique_ptr();
new (this) unique_ptr(std::move(rhs));
return *this;
}

~unique_ptr() {
if (raw_ptr_) {
delete raw_ptr_;
raw_ptr_ = nullptr;
}
}

void swap(unique_ptr<T>& rhs) {
if (this == &rhs) return;
T* tmp = raw_ptr_;
raw_ptr_ = rhs.raw_ptr_;
rhs.raw_ptr_ = tmp;
}
void reset(T* raw_ptr = nullptr) {
this->~unique_ptr();
raw_ptr_ = raw_ptr;
}

T* release() {
T* ret = raw_ptr_;
raw_ptr_ = nullptr;
return ret;
}
T* get() const { return raw_ptr_; }

T& operator*() const { return *raw_ptr_; }
T* operator->() const { return raw_ptr_; }
private:
// 不允许 copy
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;

T* raw_ptr_;
};

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
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
template <class T>
class weak_ptr;

template <class T>
class shared_ptr {
public:
shared_ptr()
: raw_ptr_(nullptr), ref_(nullptr)
{}
shared_ptr(T* raw_ptr)
: raw_ptr_(raw_ptr), ref_(new std::atomic<uint64_t>(1))
{}
// copy
shared_ptr(const shared_ptr<T>& rhs) {
if (rhs) {
raw_ptr_ = rhs.raw_ptr_;
ref_ = rhs.ref_;
ref_->fetch_add(1);
}
}
shared_ptr<T>& operator=(const shared_ptr<T>& rhs) {
if (this == &rhs) return *this;
if (rhs) {
this->~shared_ptr();
raw_ptr_ = rhs.raw_ptr_;
ref_ = rhs.ref_;
ref_->fetch_add(1);
}
return *this;
}
// move
shared_ptr(shared_ptr<T>&& rhs) {
}
shared_ptr<T>& operator=(shared_ptr<T>&& rhs) {
}
~shared_ptr() {
// 错误示例:
// if (*ref_ == 1) { // 在多线程时,可能会出现 2 个sp同时进入析构函数,然后读取 ref_ 时发现都是 2,导致内存泄漏
// delete raw_ptr_;
// } else {
// ref_->fetch_sub(1);
// }
// raw_ptr_ = nullptr;
// ref_ = nullptr;

if (*this) {
int ref = ref_->fetch_sub(1); // 析构的时候这一步很容易错
if (ref == 1) {
delete raw_ptr_;
}
raw_ptr_ = nullptr;
ref_ = nullptr;
}
}

void reset(T* raw_ptr = nullptr) {
if (*this) {
this->~shared_ptr();
} else {
new (this) shared_ptr<T>(raw_ptr);
}
}

void swap(shared_ptr<T>& lhs, shared_ptr<T>& rhs) {
if (&lhs == &rhs) return;
T* tmp1 = lhs.raw_ptr_;
std::atomic<uint64_t>* tmp2 = lhs.ref_;

lhs.raw_ptr_ = rhs.raw_ptr_;
lhs.ref_ = rhs.ref_;
rhs.raw_ptr_ = tmp1;
rhs.ref_ = tmp2;
}

T* get() const { return raw_ptr_; }
uint64_t use_count() const {
if (!*this)
return 0;
return *ref_;
}
bool unique() const {
if (!*this)
return false;
return *ref_ == 1;
}

T& operator*() const {
return *raw_ptr_;
}

T* operator->() const {
return raw_ptr_;
}

operator T*() const { return raw_ptr_; }
private:
friend class weak_ptr<T>;

T* raw_ptr_;
std::atomic<uint64_t>* ref_;
};
// shared_ptr
template <class T, class U>
bool operator==(const shared_ptr<T>& lhs, const shared_ptr<U>& rhs) {
return reinterpret_cast<long long>(lhs.get()) == reinterpret_cast<long long>(rhs.get());
}

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
48
49
50
51
52
53
54
55
56
57
58
59
template <class T>
class weak_ptr {
public:
weak_ptr() = default;
weak_ptr(const weak_ptr<T>& wp) {
*this = wp;
}
weak_ptr(const shared_ptr<T>& sp) {
*this = sp;
}

weak_ptr<T>& operator=(const weak_ptr<T>& rhs) {
raw_ptr_ = rhs.raw_ptr_;
ref_ = rhs.ref_;
}

weak_ptr<T>& operator=(const shared_ptr<T>& rhs) {
raw_ptr_ = rhs.raw_ptr_;
ref_ = rhs.ref_;
return *this;
}

// move
weak_ptr(weak_ptr<T>&& rhs) {
*this = std::move(rhs);
}

weak_ptr<T>& operator=(weak_ptr<T>&& rhs) {
*this = rhs;
rhs.raw_ptr_ = nullptr;
rhs.ref_ = nullptr;
}

// modifiers
void reset() {
raw_ptr_ = nullptr;
ref_ = nullptr;
}

void swap(weak_ptr<T>& wp) {
swap(raw_ptr_, wp.raw_ptr_);
swap(ref_, wp.ref_);
}

// observers
shared_ptr<T> lock() const {
shared_ptr<T> ret;
if (!expired()) {
ret.raw_ptr_ = raw_ptr_;
ret.ref_ = ref_;
ref_->fetch_add(1);
}
return ret;
}
bool expired() const { return ref_ == nullptr or *ref_ == 0; }
private:
T* raw_ptr_{0};
std::atomic<uint64_t>* ref_{0};
};

相关技术

owner_less

What does std::owner_less do?

enable_shared_from_this

用于获取被 shared_ptr 管理的对象的 shared_ptr;(有点绕。。。)

记住三点:

  1. enable_shared_from_this 使用了 CRTP 技术实现编译期多态;
  2. 继承了 enable_shared_from_this 的类,必须是堆对象,而不能是栈对象;
  3. 不要在派生类的构造函数里面调用 shared_from_this,因为此时还没有把它交给 shared_ptr 管理;

bad_weak_ptr

这是一个异常类,继承自 std::exception

std::bad_weak_ptr is the type of the object thrown as exceptions by the constructors of std::shared_ptr that take std::weak_ptr as the argument, when the std::weak_ptr refers to an already deleted object.——cppreference

上面的话的意思就是:std::bad_weak_ptr 是在调用构造函数 shared_ptr(const weak_ptr<T>& rhs) 时,因为 std::weak_ptr 所指的对象已经被删除而抛出的异常对象;

Member Function 中只需要记住一个:std::bad_weak_ptr::what(基类的虚函数);

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <memory>
#include <iostream>
int main()
{
std::shared_ptr<int> p1(new int(42));
std::weak_ptr<int> wp(p1);
p1.reset();
try {
std::shared_ptr<int> p2(wp);
} catch(const std::bad_weak_ptr& e) {
std::cout << e.what() << '\n';
}
}

default_delete

智能指针中的删除器,用于 unique_ptr 和 shared_ptr(只有这两个有 ownership);默认情况下是,std::default_delete<T>()std::default_delete<T[]>();前者的默认行为是 delete,后者的默认行为是 delete [];

可以在构造 unique_ptr 的时候指定 default_delete:

1
2
3
4
5
6
7
template<
class T,
class Deleter = std::default_delete<T>
> class unique_ptr;

std::unique_ptr<int> up(new int(10), std::default_delete<int>());
std::unique_ptr<int> up(new int[10], std::default_delete<int[]>());

可以看到, unique_ptr 模板类的模板参数中就带有 Deleter;

再看下 shared_ptr:

1
2
3
4
5
6
7
8
9
10
11
12
template< class T > class shared_ptr;

template< class Y, class Deleter >
shared_ptr( Y* ptr, Deleter d );

std::shared_ptr<int> sp(new int(10), std::default_delete<int>());
std::shared_ptr<int> sp(new int[10], std::default_delete<int[]>());

std::vector<int*> v;
for(int n = 0; n < 100; ++n)
v.push_back(new int(n));
std::for_each(v.begin(), v.end(), std::default_delete<int>());

shared_ptr 模板类的模板参数中仅有一个资源类型参数,没有 Deleter;它是在构造函数多了个模板参数,它的构造函数是函数模板;

out_ptr_t

inout_ptr_t

Understanding std::inout_ptr and std::out_ptr in C++23