C++ 智能指针

导语
本篇不对智能指针的实现原理做详细研究,只讨论为什么要使用智能指针,以及使用规范和注意事项。

关于指针和智能指针

指针是一个变量,存储一块内存区域的地址。
指针指向一块内存,通过该指针可随意修改或者删除其中内容,那么我们可以说这个指针对这块内存具有所有权(ownership) 。

PS:通常说的指向一个对象的指针,严格意义上来说应该是指向一个对象所在内存的指针。指针可以说和对象本身是没关系的,只是对象的类型(class)更详细的描述这块内存区域存的是什么内容。

指针存在的意义:
1.访问特定内存中的数据
2.管理所拥有的内存,不再使用的内存要及时通知系统释放

裸指针能满足以上的访问和管理内存的需求,但是存在缺陷:
多个指针可以指向同一块内存
也就是意味着权限混乱,比如创建(new)多个指针指向同一块内存,但是每个指针使用完毕,都没有释放(delete),从而导致内存泄露。
或者所有的指针只要自己使用完毕就去释放内存,势必会造成正在使用该内存的地方产生未知结果,或者重复释放(delete)同一块内存导致崩溃。

案例一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class 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
#include <iostream>
#include <memory>
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
16
class 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
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
#include <iostream>
#include <memory>
using namespace std;

class Entity
{
private:
int id=-1;
public:
int getId()
{return id;}
Entity(int id_)
{
id=id_;
cout << "Entity(id"<< id <<") construct!" << endl;
}
~Entity()
{
cout << "~Entity(id"<< id <<") destruct!" << endl;
}

void DoSomething(){ cout << " Do something " << endl;}

};


shared_ptr<Entity> bar()
{
auto temp_entity = make_shared<Entity>(2);
return temp_entity; // 移动所有权
}

void foo(shared_ptr<Entity> entity_)
{
cout << "entity"<< entity_->getId() <<"转移所有权到函数后计数"<< entity_.use_count()<< endl;
entity_->DoSomething();
}
int main()
{

{
cout << "========enter scope 1=========" << endl;
shared_ptr<Entity> entity0 = make_shared<Entity>(0); //推荐用法创建共享指针

auto entity1 = make_shared<Entity>(1); //推荐用法创建
cout << "entity1创建赋值后计数"<< entity1.use_count()<< endl;
auto entity2 = entity1;
cout << "entity1复制赋值后计数"<< entity1.use_count()<< endl;
auto entity3 = move(entity2); // 移动所有权
cout << "entity1转移所有权后计数"<< entity1.use_count()<< endl;
foo(move(entity3)); // 移动所有权
cout << "entity1离开函数作用域后计数"<< entity1.use_count()<< endl;
foo(bar());

entity1.reset(/* p */); // 释放内存,并置为nullptr。如果传入一个裸指针p作为参数,则释放内存后重新指向p。
entity1 = nullptr; // 释放内存,并置为nullptr。
cout << "========leave scope 1=========" << endl;
}

cout << "========leave main scope=========" << endl;
return 0;
}

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++ 中一切皆对象),可以说指向一个空对象,可以说指向的对象被销毁了。我觉得都没问题,因为指针虽然是一个地址,但是指针大部分情况是带着类型信息的,就是用来描述所指向的内存中的数据的组织方式,类型信息决定了如何正确使用这块内存中的记录的一堆东西。

------------- 感谢您的阅读-------------
作者dreamingpoet
有问题请发邮箱 Dreamingoet@126.com
您的鼓励将成为创作者的动力