cpp八股2

实现三个智能指针

首先是 UniquePtr,它是独占式的智能指针,不能被复制,只能被移动。

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
template<typename T>
class UniquePtr
{
public:
UniquePtr(T* p = nullptr) : ptr(p) {}
UniquePtr(const UniquePtr&) = delete;
UniquePtr& operator=(const UniquePtr&) = delete;
UniquePtr(UniquePtr&& other) noexcept
: ptr(other.ptr)
{
other.ptr = nullptr;
}
UniquePtr& operator=(UniquePtr&& other) noexcept
{
if(this == &other) return *this;

delete ptr;
ptr = other.ptr;
other.ptr = nullptr;
return *this;
}
~UniquePtr() { delete ptr; }
T* get() const { return ptr; }
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
T* release()
{
T* tmp = ptr;
ptr = nullptr;
return tmp;
}
void reset(T* p = nullptr)
{
delete ptr;
ptr = p;
}
operator bool() const { return ptr != nullptr; }
private:
T* ptr;
};

在C++03中引入了一个 std::auto_ptr,它的思想也是将资源独占。但是它的实现内允许使用复制构造函数和复制赋值运算符,这会导致一些问题,例如在函数传参时会发生资源的转移,导致原来的指针变成空指针。以及它和STL容器结合会产生一堆未定义行为,例如一个 std::vectorstd::auto_ptr ,如果使用 reserve 或者扩容触发内存重新分配,会导致可能一部分 std::auto_ptr 为空。

后续C++11引入了右值引用和移动语义的概念,这十分符合资源独占的需求,因此 std::auto_ptr 被废弃了,取而代之的是 std::unique_ptr

然后是 SharedPtr,它是共享式的智能指针,允许多个指针共享同一个资源,通过引用计数来管理资源的生命周期。

前面的文章已经给了一个实现,但是我们这里由于要同时实现 WeakPtr ,而如果将进行引用计数特化为另一个类 ControlBlock 的话,那么 SharedPtrWeakPtr 就能够共享这个 ControlBlock,实现会更加自然:

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
template<typename T>
class ControlBlock
{
public:
explicit ControlBlock(T* p) : ptr(p), use_count(1), weak_count(0) {}
// 删除析构函数,让release_ref()单独管理内存

T* get() const { return ptr; }
size_t get_use_count() const { return use_count; }

void add_ref() { ++use_count; }
void release_ref()
{
if(--use_count == 0)
{
delete ptr; // 只在这里删除对象
ptr = nullptr;
if(weak_count == 0)
delete this; // 所有引用都结束后才删除控制块
}
}
void add_weak() { ++weak_count; }
void release_weak()
{
if(--weak_count == 0 && use_count == 0)
delete this;
}
private:
T* ptr;
size_t use_count;
size_t weak_count;
};

template<typename T>
class WeakPtr;

template<typename T>
class SharedPtr
{
friend class WeakPtr<T>;
public:
// 构造函数
explicit SharedPtr(T* p = nullptr)
: control(p ? new ControlBlock<T>(p) : nullptr) {}

// 拷贝构造
SharedPtr(const SharedPtr& other) : control(other.control)
{
if(control)
control->add_ref();
}

// 拷贝赋值
SharedPtr& operator=(const SharedPtr& other)
{
if(this != &other)
{
// 先释放旧的引用
if(control)
control->release_ref();

// 再获取新的引用
control = other.control;
if(control)
control->add_ref();
}
return *this;
}

// 移动构造
SharedPtr(SharedPtr&& other) noexcept : control(other.control)
{
other.control = nullptr;
}

// 移动赋值 (返回左值引用,不是右值引用)
SharedPtr& operator=(SharedPtr&& other) noexcept
{
if(this != &other)
{
if(control)
control->release_ref();
control = other.control;
other.control = nullptr;
}
return *this;
}

// 析构函数
~SharedPtr()
{
if(control)
control->release_ref();
}

// 获取原始指针
T* get() const { return control ? control->get() : nullptr; }

// 解引用
T& operator*() const { return *get(); }
T* operator->() const { return get(); }

// 获取引用计数
size_t use_count() const { return control ? control->get_use_count() : 0; }

// bool转换
explicit operator bool() const { return get() != nullptr; }

// 重置
void reset(T* p = nullptr)
{
if(control)
control->release_ref();
control = p ? new ControlBlock<T>(p) : nullptr;
}

private:
ControlBlock<T>* control;
};

template<typename T>
class WeakPtr
{
public:
// 默认构造
WeakPtr() : control(nullptr) {}

// 从SharedPtr构造
template<typename U>
WeakPtr(const SharedPtr<U>& shared) : control(shared.control)
{
if(control)
control->add_weak();
}

// 拷贝构造
WeakPtr(const WeakPtr& other) : control(other.control)
{
if(control)
control->add_weak();
}

// 拷贝赋值
WeakPtr& operator=(const WeakPtr& other)
{
if(this != &other)
{
if(control)
control->release_weak();
control = other.control;
if(control)
control->add_weak();
}
return *this;
}

// 移动构造
WeakPtr(WeakPtr&& other) noexcept : control(other.control)
{
other.control = nullptr;
}

// 移动赋值
WeakPtr& operator=(WeakPtr&& other) noexcept
{
if(this != &other)
{
if(control)
control->release_weak();
control = other.control;
other.control = nullptr;
}
return *this;
}

// 析构函数
~WeakPtr()
{
if(control)
control->release_weak();
}

// 获取SharedPtr
SharedPtr<T> lock() const
{
if(control && control->get_use_count() > 0)
{
SharedPtr<T> sp;
sp.control = control;
control->add_ref();
return sp;
}
return SharedPtr<T>();
}

// 检查对象是否已销毁
bool expired() const
{
return control == nullptr || control->get_use_count() == 0;
}

// 获取引用计数
size_t use_count() const { return control ? control->get_use_count() : 0; }

private:
ControlBlock<T>* control;
};

面试的时候如果被问到智能指针的实现细节,我会将 UniquePtr 单独拎出来讲,它的Big Five中的拷贝构造和拷贝赋值直接删除掉,移动构造和移动赋值实现资源转移,析构函数负责释放资源。可以提一下 std::auto_ptr 的问题,以及为什么被废弃了。然后讲 SharedPtrWeakPtr 的实现细节,重点讲解 ControlBlock 的设计,俩智能指针的Big Five怎么写,以及如何通过引用计数来管理资源的生命周期。

也可以稍微提一提RAII相关的东西。