RALL
一种利用对象生命周期来控制程序资源的简单技术;在对象构建时获取资源,接着控制对资源的访问使之在对喜爱那个的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际把管理一份资源的责任托管给了一个对象。这样做的好处:
- 不需要显示地释放资源
- 采用这种方式,对象所需的资源在其生命期内始终保持有效
智能指针概念
在C++中,动态内存的管理是通过一对运算符来完成的;new:在动态内存中为对象分配空间并返回一个指向该对象的指针;delete:接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。
智能指针本质是一个类模版,它可以创建任意的类型的指针对象,当智能指针对象使用完后,对象就会自动调用析构函数去释放该指针所指向的空间。
智能指针模版中都需要包含一个指针对象,构造函数和析构函数。
智能指针的定义和使用
智能指针的使用和普通指针类似,可以使用运算符和->取获取指向的对象,所以我们要在类中重载和->函数。
如果类中没有定义拷贝构造函数和赋值重载函数,那么就只能调用类中原生的拷贝构造函数和赋值重载函数,程序将会崩溃:
因为两个指针指向了同一块空间,ptr2被销毁时,他会调用他的析构函数取delete该资源,ptr1被销毁时,也会去调用它的析构函数取释放ptr1所指向的资源。该资源被释放两次,程序崩溃;
所以我们不能使用原生的拷贝构造函数和赋值重载函数,并且定义的拷贝构造函数和赋值重载函数需要考虑只能释放一次资源对象。
C++库中的智能指针
auto_ptr
C++98版本中提供的智能指针,该指针解决上方问题的措施是管理权转移的思想,也就是原对象拷贝给新对象时,源对象会被设置为nullptr,此时就只有新对象指向一块资源空间。
由于资源空间的管理权经过拷贝之后已经转移,如果再去使用原来对象的话,程序将会崩溃;
auto_ptr的拷贝构造函数和赋值重载函数的实现
unique_ptr
unique_ptr是C++11版本库提供的智能指针,他直接将拷贝函数和赋值重载函数给禁用掉,因此不能够进行拷贝和赋值;
unqiue_ptr的拷贝函数和赋值重载函数给禁掉了
shared_ptr
share_ptr是C++11版本库中的智能指针,shared_ptr允许多个智能指针可以指向同一块资源,并且能够保证共享的资源只会被释放一次,因此程序不会崩溃掉;
shared_ptr的原理
shared_ptr采用的是计数原理来实现多个shared_ptr对象之间共享资源;
shared_ptr在内部会维护一份引用计数,用来记录该份资源被几个对象共享。
当一个shared_ptr对象被销毁时(调用析构函数),析构函数就会将该数减1;
如果引用计数减为0后,则说明自己是最后一个使用该资源的shared_ptr对象,必须释放资源
如果引用计数不为0,则说明还有其他对象在使用,则不能释放该资源,否则其他对象就成为了野指针。
引用计数是用来记录资源对象中有多少个指针指向该资源对象。
shared_ptr的实现
赋值重载的三种情况:
- ptr1=ptr1:智能指针自己给自己赋值,不做处理
- ptr2=ptr1:如果ptr2和ptr1指向同一块空间,不做处理
- ptr2=ptr1:如果ptr2和ptr1指向的空间不一样,处理过程如下:
因为ptrcount指向的对象是在堆上,隐私所有的线程都能够访问到该资源,多线程在修改ptrcount时,则会出现线程安全问题,因此需要在修改ptrcount时需要用锁来保证期数据的正确性。
“*”会返回ptr指向的对象,为什么不需要锁对其进行保护,因为ptr返回的对象有可能被读或者被写,这个不是指针内部所考虑到,而是调用者进行考虑的;
shared_ptr的循环引用
当如果使用智能指针做一个双向链表时,节点内部的pre和next指针也要定义为shared_ptr的智能指针,如果定义为普通指针,则不能够赋值给普通的指针。
当两个节点相互引用的时候,就会出现循环引用的现象。
use_count():返回智能指针对象的引用次数;
- 当创建出node1和node2智能指针对象时,引用计数都是1;
- 当node1的next指向next2所指向的资源时,node2的引用计数就+1,变成2;node2的pre指向node1所指向的资源时,node1的引用次数+1,变成2;
- 当这两个智能指针使用完后,调用析构函数,引用计数都-1,变成了1,由于不为0,所以node1和node2指向的对象不会被释放;
- 当node1所指向的资源释放需要当node2中的prev被销毁,就需要node2资源的释放,node2所指向资源的释放就需要node1中的next被销毁,就需要node1资源的释放,就造成了循环引用问题,最终node1和node2都不会进行释放。
如何解决循环引用问题?
C++库中存在weak_ptr类型的智能指针,weak_ptr类的对象可以指向shared_ptr,并且不会改变shared_ptr的引用计数,一旦最后一个share_ptr被销毁,对象就会释放。
wwak_ptr对象指向shared_ptr对象时,不会增加shared_ptr中的引用计数,因此当node1被销毁掉时,则node1指向的空间就会被销毁,解决循环引用的问题。
所以在定义双向链表或者二叉树等有多个指针的时候,如果想要将该类型定义为智能指针,那么结构体内的指针需要定义成weak_ptr类型的指针,防止循环引用的出现。
weak_ptr的简单实现
定制删除器
当我们释放一个指针数组的指针时,delete后面的[]必须存在。它指示编译器此指针指向的是一个对象数组的第一个元素,如果我们忽略了在delete一个指向数组的指针中忽略了[],我们的程序可能在执行过程中在没有任何警告下行为异常。
如果我们在动态内存中创建一个数组,用一个shared_ptr对象去指向该数组,当shared_ptr使用完后,就会调用析构函数,由于shared_ptr默认的删除方式是delete ptr,后面没有带方括号,那么程序就会崩溃。
如果我们打开了一个文件,返回了一个文件指针,让一个shared_ptr对象去指向该文件,那么在调用析构函数的时候就不能采用delete方法,而是使用flose()函数去关闭该文件。
因此,shared_ptr类中提供了一个构造函数可以自定义一个删除器去指定析构函数的删除方式。
这个自定义删除器可以是函数指针,仿函数、lamber、包装器。
仿函数的删除器
shared_ptr中的析构函数会去调用DelArry仿函数去释放动态数组