智能指针,从其本质上说,就是要控制对象的销毁时机。换句话讲就是何时调用对象的析构函数。从C++11开始引入三个智能指针(unique_ptr
,shared_ptr
,weak_ptr
),准确的说是四种(还有auto_ptr
),但从C++17开始auto_ptr
被移除了。所以就剩下上述三种了。所有的智能指针都包含在memory
头文件中。
首先,很久以前的C++是没有智能指针的,用户创建在堆上的内存,智能自己显示的释放,如果没有释放就会造成内存泄漏。这种特点导致C++的使用成本很高,为了降低成本,引入了智能指针unique_ptr
和shared_ptr
。
unique_ptr
unique_ptr
采用的是传递所有权的方式来控制对象的销毁时机。如其名字所示,其对象是独享的。
1 | class Simple { |
unique_ptr
禁用拷贝构造
由于unique_ptr
禁用了拷贝构造函数unique_ptr(const unique_ptr&) = delete;
,所以一切试图触发拷贝构造函数的操作都会引发编译错误。unique_ptr
所有权一旦变更就不能使用原指针访问对象资源
上述代码中有两处使用了原指针访问对象资源,第一处s_ptr->showfunc();
没有报错,可以正常打印;s_ptr->Show();
中使用到了成员变量m_a
所以会导致报错“this
空指针”。这是由于std::move
的赋值操作触发了unique_ptr
中的Move构造函数(unique_ptr(unique_ptr&&) = default;
),从而将s_ptr
中的成员清空。所以再次访问原对象指针,就会出错。
shared_ptr
shared_ptr
采用的是引用计数的机制来控制对象的销毁时机。如其名字所示,其对象是共享的。当计数器等于0是,调用对象的析构函数。
1 | void shared_test_inner(std::shared_ptr<Simple> *steal_ptr) { |
尽可能使用make_shared
创建shared_ptr
,如果使用std::shared_ptr<Simple> s_ptr(new Simple(1))
创建shared_ptr
,需要分配两次内存,一次是new Simple(1)
;另一次是shared_ptr
的引用计数。make_shared
只分配一次。
现在创建一个SimpleBack
类,然后再让Simple
和SimpleBack
循环引用。
1 | class SimpleBack; |
通过析构函数函数的打印语句可以看出s
和sb
并没有被析构,这说明s
和sb
泄漏了。
为了解决shared_ptr
在循环依赖中内存泄漏的问题,推出了weak_ptr
。
weak_ptr
weak_ptr
不会增加引用计数,不能直接操作对象的内存(需要先调用lock
接口),需要和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
41class SimpleBack;
class Simple {
private:
int m_a;
public:
void Show() {
printf("Hello Simple %d\n", m_a);
}
void showfunc() {
printf("Hello Simple func \n");
}
Simple(int n) {
m_a = n;
printf("Simple Construct\n");
}
Simple(const Simple& p) {
printf("Simple Copy Construct\n");
}
~Simple() {
printf("Simple Destroy\n");
}
std::shared_ptr<SimpleBack> m_sb;
};
class SimpleBack {
public:
// 将循环引用的其中一个改成weak_ptr
std::weak_ptr<Simple> m_s;
~SimpleBack() {
printf("SimpleBack Destroy\n");
}
};
void shared_test_leak() {
auto s = std::make_shared<Simple>(2);
auto sb = std::make_shared<SimpleBack>();
s->m_sb = sb;
sb->m_s = s;
}
通过析构函数的打印语句可以看出,s
和sb
的析构函数在shared_test_lead()
调用结束后被调用。
那么,weak_ptr
的使用是不是也像shared_ptr
一样呢?不是的。weak_ptr
需要与shared_ptr
配合使用,看一个例子。
1 | void weak_test() { |
lock
若对象已被析构,则返回一个空的shared_ptr
;否则返回实际的shared_ptr
。expired
若对象已被析构,则返回true
;否则返回false