一、从new和delete谈起
在C++中,可以使用new和delete关键字进行对象的创建和销毁,new一个对象实际上是在堆上分配内存,而new出来的对象也要自己用delete释放,从而回收内存,否则会造成内存的泄露。由程序员自己new来分配对象内存的方式成为动态分配。
其中有两个要点
- new和delete需要成对使用,有new必然有delete,否则会导致内存泄漏,没有new分配的内存,不能使用delete来释放。
- delete一块内存,只能delete一次,不可以delete多次,因为第一次delete之后,这块内存被回收会被分配给其他变量。可以在delete一个指针后将该指针设置为空
ptr = nullptr
。因为一个指针指向的内容即便被delete,该指针中依然保存着它所指向的那块动态内存地址,此时该指针被称为选空指针。如果此时将指针置空,则表示该指针不指向任何内存,这是一个良好的编程习惯
在使用动态分配时经常会有以下问题:
- 内存泄漏:new了的对象再使用完后忘记delete导致内存一直被占用,相当于吃完饭不收盘子
- 重复删除:对已经delete的对象进行再次delete,但是原内存空间被分配给了其他对象,导致回收到了其他的内存空间,相当于吃完饭把别人的盘子收了
- 回收冲突:A和B均需要使用对象a,A使用完之后就顺手delete了a,但是B还需要使用a。相当于别人没吃完自己吃完,先把盘子收走了
总的来说,对内存进行管理依然是会让初学者头大的一个挑战,因此C++新标准中出现了智能指针。
二、智能指针
直接使用new一个对象的方式返回的是一个对象的指针,这个对象指针称为裸指针,这种指针功能强大使用灵活,但是需要全程负责维护,容易出错。在C++ 11中引入了新的指针——智能指针,智能指针对裸指针进行了包装,最突出的特性是智能指针能够自动释放所指向的对象。
C++标准库中规定了四种智能指针,分别是std::auto_ptr, std::unique_ptr, std:shared_ptr, std:weak_ptr
,其中std::auto_ptr
是C++ 98推出的,目前该智能指针已被std:unique_ptr
取代,C++ 11中也明确弃用了该种智能指针。这三种指针其实都是类模板,可以将new获得的地址赋予给他们
- shared_ptr是共享指针的概念,多个指针指向同一个对象,最后一个指针被销毁时,这个对象会被释放。
- weak_ptr主要用于辅助shared_ptr工作
- unique_ptr是一种独占式指针的概念,同一个时间内只能由一个指针指向该对象
2.1 shared_ptr
shared_ptr指针采用共享所有权来管理所指向对象的生存期,对象可以被多个shared_ptr指向,多个shared_ptr之间互相协作,从而确保只在不需要所指对象的时候才回收对象。shared_ptr的工作机制是引用计数,每一个shared_ptr指向相同的对象都会增加引用计数值,知道最后一个指向该对象的shared_ptr指针不再指向该对象的时候,才会析构对象
智能指针是一个类模板,使用尖括号规定指针指向的类型,使用形式如下
shared_ptr<指向的类型> 智能指针名// 样例
shared_ptr<string> p1; //一个指向string的智能指针
当然,智能指针也可以进行带初值的初始化
shared_ptr<int> pi(new int(100));
这是使用裸指针初始化智能指针的场景,但是还会遇到一些陷阱,因此shared_ptr模板中内置了make_shared函数进行初始化,是最安全并且更高效的初始化方法,它能够在动态内存(堆)中分配并且初始化一个对象,然后返回指向此对象的shared_ptr,使用方法如下:
shared_ptr<int> p2 = std::make_shared<int>(10);
shared_ptr<string> p3 = std::make_shared<string>(10, 'hello');
2.1.2 shared_ptr常规操作
1.引用计数的增加
每个shared_ptr会记录有多少个其他shared_ptr指向同样的对象
(1) 使用智能指针对另一个智能指针进行初始化
auto p6 = std::make_shared<int>(100);
auto p7 = p6;
(2)把智能指针作为实参往函数中传递
void myfunc(shared_ptr<int> ptmp){return;
}myfunc(p7);
这会增加指针引用数,当函数返回时减少
(3)作为函数返回值
shared_ptr<int> myfunc(shared_ptr<int> ptmp){return ptmp;
}p8 = myfunc(p7);
2.引用计数减少
(1)shared_ptr指向新对象的时候,引用计数减少
(2)局部智能指针离开其作用域,比如上面(2)中智能指针作为实参传递进函数时增加引用数,函数执行完毕后减少
(3)当一个shared_ptr引用技术变为0的时候,会自动释放自己管理的对象
3.常规操作
(1)use_count成员函数:返回有多少个智能指针指向某个对象
(2)unique成员函数,是否仅有一个智能指针指向某对象
(3)reset成员函数,不带参数时,pi置空,原对象引用计数-1;带参数则将该智能指针指向参数中的对象
*4.解引用
返回智能指针p指向的对象
5.get成员函数
p.get()用于返回智能指针p中保存的指针,小心使用,若智能指针释放了所指向的对象,则返回的指针所指的对象也会失效。
2.2 weak_ptr简介
weak_ptr是一个智能指针,这种智能指针指向一个由shared_ptr管理的对象,但是这种指针并不控制所指向对象的生存期,也不会改变shaed_ptr的引用计数。使用样例如下:
auto pi = make_shared<int>(100);
weak_ptr<int> piw(pi);
既然weak_ptr所指向的对象有可能不存在,那么waek_ptr是不能直接用于访问对象的,必须要使用一个叫做lock的成员函数,lock的功能是检查weak_ptr所指向的对象是否还存在,如果是,lock能够返回一个指向共享对象的shared_ptr,如果不存在,则返回一个空的shared_ptr
常用操作
1.use_count成员函数
获取该弱指针共享对象的其他shared_ptr数量,或者说强引用计数的数量
2.expired
若该指针的use_count为0,则返回true,反则返回false
3.reset成员函数
将该弱指针设置为空,不影响该对象的强引用数量,但是指向该对象的弱引用数量减1
4.lock
获取一个弱指针所指向的对象的shared_ptr
2.2.1 shared_ptr和weak_ptr的大小
weak_ptr和shared_ptr的尺寸都是裸指针的2倍大小,在x86平台下,一个裸指针的sizeof时4Bytes,因此weak_ptr和shared_ptr的大小都是8Bytes。其实这8个字节包含了2个裸指针,如图示:
其中第一个裸指针指向智能指针所指向的对象,第二个指针指向一个控制块,这个控制块中有:1.所指对象的引用计数 2.所指对象的弱引用计数 3.其他数据,比如自定义的删除器指针
2.3 unique_ptr简介和常规操作
2.3.1 unique_ptr简介
unique_ptr智能指针是一种独占式智能指针,同一个时刻,只有一个unique_ptr指针指向这个对象,当这个unique_ptr被销毁的时候,它所指向的对象也会被销毁。
1.常规初始化(unique_ptr和new配合)
unique_ptr<int> pi;
unique_ptr<int> pi2(new int(105));
2.make_unique函数
C++ 11中没有make_unique函数,但是C++ 14提供了这个函数
和常规的初始化相比,要优先选择make_unique函数,这能有更好的性能。但是如果使用make_unqiue的话,就不能使用删除器,因为make_unique不支持指定删除器的语法
unique_ptr<int> p1 = std::make_unique<int>(100);
auto p2 = std::make_unique<int>(200);
2.3.2 unique_ptr常用操作
1.unique_ptr不支持的操作
unique_ptr<string> ps1(new string("I Love China!));
unique_ptr<string> ps2(ps1); // 不支持复制
unique_ptr<string> ps3 = ps1; // 不支持复制
unique_ptr<string> ps4;
ps4 = ps1; // 不支持赋值动作
unique_ptr不允许复制、赋值等操作,是一个只能移动不能复制的语义
2.移动语义
可以通过std:move
将一个unique_ptr转移到其他的unique_ptr中
unique_ptr<string> ps1(new string("I Love China!));
unique_ptr<string> ps3 = std::move(ps1); // 转移后ps3为空了,ps3指向原来的ps1所指
3.release成员函数
放弃对指针的控制权,切断智能指针和其所指向对象之间的联系。返回指针,将智能指针置空,返回的裸指针可以手工delete食坊,也可以用于初始化另一个智能指针,或者给另外一个智能指针赋值。
4.reset成员函数
当reset不带参数的时候,释放智能指针指向对象,并且将智能指针置空,当reset带参数时,释放智能指针指向原来的内存,让该智能指针指向新内存。
5.=nullptr
释放智能指针所指向的对象,并且将智能指针置空
6.指向一个数组
std::unique_ptr<int[]> ptr_arr(new int[10]);
ptr_arr[0] = 12;