导语
本篇不对智能指针的实现原理做详细研究,只讨论为什么要使用智能指针,以及使用规范和注意事项。
关于指针和智能指针
指针是一个变量,存储一块内存区域的地址。
指针指向一块内存,通过该指针可随意修改或者删除其中内容,那么我们可以说这个指针对这块内存具有所有权(ownership) 。
PS:通常说的指向一个对象的指针,严格意义上来说应该是指向一个对象所在内存的指针。指针可以说和对象本身是没关系的,只是对象的类型(class)更详细的描述这块内存区域存的是什么内容。
指针存在的意义:
1.访问特定内存中的数据
2.管理所拥有的内存,不再使用的内存要及时通知系统释放
裸指针能满足以上的访问和管理内存的需求,但是存在缺陷:
多个指针可以指向同一块内存
也就是意味着权限混乱,比如创建(new)多个指针指向同一块内存,但是每个指针使用完毕,都没有释放(delete),从而导致内存泄露。
或者所有的指针只要自己使用完毕就去释放内存,势必会造成正在使用该内存的地方产生未知结果,或者重复释放(delete)同一块内存导致崩溃。
案例一:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Entity;
void foo(Entity* e)
{
// e 的生命周期有多长,在我使用过程中会被其他函数删除吗?
// 我用完了e,需要删除它吗?删除它会对其他引用e 的地方造成未知影响吗?
}
void bar()
{
auto e = new Entity();
e->dosomething();
foo(e);
}
bar();
// 内存泄露了,e 的内存没有被释放
智能指针就是为了解决这些问题而出现的。
意味着不再使用 new 和 delete。
三种智能指针:
unique_ptr 独占指针
shared_ptr 共享指针
weak_ptr 弱指针
unique_ptr
同一时刻只能有一个unique_ptr指针指向一块内存;
独占所有权,所有权可以转移,转移之后该指针不可使用;
离开作用域之时,指向的内存自动释放(默认使用delete操作符,用户可指定其他操作)。
(通过禁止拷贝语义、只有移动语义来实现)。
基本操作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
using namespace std;
class Entity
{
private:
public:
Entity()
{
cout << "Entity() construct!" << endl;
}
~Entity()
{
cout << "~Entity() destruct!" << endl;
}
void DoSomething(){ cout << " Do something " << endl;}
};
unique_ptr<Entity> bar()
{
auto temp_entity = make_unique<Entity>();
return temp_entity; // 转移所有权
}
void foo(unique_ptr<Entity> entity_)
{
entity_->DoSomething();
}
int main()
{
{
cout << "========enter scope 1=========" << endl;
unique_ptr<Entity> entity1 = make_unique<Entity>(); //推荐用法创建唯一指针
auto entity2 = make_unique<Entity>(); //推荐用法创建
auto entity3 = move(entity2); // 转移所有权
foo(move(entity3)); // 转移所有权
foo(bar());
Entity* rawe = entity1.release(); // 放弃所有权,并置为nullptr,不释放内存,返回对象的裸指针。请慎重操作
delete rawe;
entity1.reset(/* p */); // 释放内存,并置为nullptr。如果传入一个裸指针p作为参数,则释放内存后重新指向p。
entity1 = nullptr; // 释放内存,并置为nullptr。
cout << "========leave scope 1=========" << endl;
}
cout << "========leave main scope=========" << endl;
return 0;
}
用智能指针来实现文章开头的案例一1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Entity;
void foo(unique_ptr<Entity> e)
{
e->DoSomething();
// e 离开作用域会自动释放内存
}
int bar()
{
auto e = make_unique<Entity>(); // 创建智能指针
e->dosomething();
foo(move(e)); // 转移所有权
// e 的权限被转移,以下不可以使用e
}
bar();
// 没有内存泄露
shared_ptr
多个shared_ptr指向同一块内存,当所有shared_ptr都全部释放时,该内存释放。每个shared_ptr都对这块内存有所有权。
简单实现原理:
一个对象被shared_ptr指针引用的次数,由一个计数对象来记录。每次复制一个shared_ptr,计数+1,每次释放(主动调用或者离开作用域时)一个shared_ptr,计数-1,当计数为0时,释放该对象所在内存。
基本操作:
1 |
|
weak_ptr
weak_ptr 只能和shared_ptr搭配使用的。
弱指针的弱是相对于shared_ptr和unique_ptr对对象的所有权来说的。shared_ptr和unique_ptr的所有权关乎所指对象的生死(销毁)。
weak_ptr只是提供一种访问对象的途径,没有对对象的所有权。通俗点可以说先用weak_ptr占个坑位,但并不能直接使用它。
使用之前先检查所指向的对象是否已经销毁( expired() ),如果没销毁就通过调用weak_ptr.lock()获得一个shared_ptr同时对象的引用计数+1。
至于weak_ptr,有几个关键词,循环引用,多线程保活,比较复杂,但是又不是很常用,所以打算另写一篇来记录。
其他
关于指针到底指向什么?
指针就是一块内存区域的地址,所以可以说指向一块内存区域是没问题的。
但是有的时候又希望表述为指向一个对象(c++ 中一切皆对象),可以说指向一个空对象,可以说指向的对象被销毁了。我觉得都没问题,因为指针虽然是一个地址,但是指针大部分情况是带着类型信息的,就是用来描述所指向的内存中的数据的组织方式,类型信息决定了如何正确使用这块内存中的记录的一堆东西。