目录
智能指针原理
RAII
Unique_ptr
定制删除器
在C++库里提供的智能指针有跟多,如下图所示,使用时需要包含头文件<memory>。下面将详细介绍这些智能指针的底层原理和缺点,还有每个智能指针的应用场景。
智能指针原理
RAII
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法的好处在于:
- 1、不需要显式地释放资源。
- 2、采用这种方式,对象所需的资源在其生命期内始终保持有效。
智能指针的思想就是上述RAII的技术。智能指针雏形就相当于把一个指针存储在一个类中,当这个类的生命周期结束时,会调用它的析构函数,在析构函数中我们去释放这个指针所指向的空间,这样就避免了我们new了空间忘记delete。
在这个类内,我们可以进行运算符重载,把operator*、operator->、operator[]都重载上,这样才实现了一个完整的指针的效果。
上述这些指针的使用效果都很容易实现,最难的部分是智能指针的拷贝,如果我们拷贝了一个智能指针,这两个智能指针会管理同块区域,当两个智能指针析构时,会调用两次析构函数,也就是释放两次空间,正常情况下这样是会报错的。
在C++98版本的auto_ptr底层为了实现智能指针的拷贝构造,它会把被拷贝的智能指针的管理权转移到拷贝的智能指针,原智能指针置空。如下图所示:
显然,C++98这个设计是失败的,被拷贝的智能指针(上图sp1)被置空了,不能再次使用。
所以在C++11的智能指针(unique_ptr)进行了优化。在unique_ptr中直接明确规定不允许拷贝构造,这在auto_ptr中是没有规定的。
Unique_ptr
Unique_prt的定义中直接规定了没有拷贝构造,同理也没有赋值重载。
使用unique_ptr创建数组指针时,模板传参也必须是:类型[],要不然编译器只会析构一次,不会析构整个数组(数组要析构多次)shared_ptr使用方法也一样。
unique_ptr没有不允许拷贝,这也是有缺陷的,一个能正常使用的指针就是可以进行拷贝的,后续在share_ptr中对此进行了优化
Shared_ptr
Shared_ptr允许拷贝构造和赋值重载。它在底层加入一个引用计数,即当有几个智能指针同时指向这个空间时,引用计数便是几。在析构时,先减少引用计数,直到引用计数为1时,才调用delete去释放空间。所以shared_ptr有一个函数,名为:use_count可以返回引用计数是多少。
也是因为Shared_ptr允许赋值重载,所以有有一种新的方法来创建智能指针(unique_ptr和auto_ptr都没有)
Shared_ptr缺点
Shared_ptr不支持循环引用,循环引用是指,我(p1)的成员中有智能指针指向(管理)你(p2),你(p2)的成员中有智能指针指向(管理)我,这样会使我(p1)和你(p2)的引用计数都变成2,在析构的时候就和相互影响,导致析构失败,出现内存泄漏,具体情况如下图所示:
为了解决shared_ptr的缺陷,C++有新加入了一个智能指针weak_ptr,但weak_ptr本质上不算智能指针,它不支持RAII,不能单独管理资源(引用计数不增加),它专门来解决上述shared_ptr的循环引用。
将类内的成员智能指针改为weak_ptr就可以正常使用了(外面还是share_ptr)
weakl_ptr的底层解决方法就是,当我们对智能指针循环引用时,不增加内部的引用计数。
Weak_ptr还有一个特殊的成员函数,expired,这个成员函数能够返回它指向的资源是否还存在(有没有被释放)。返回1代表资源已经被释放,0则相反。
定制删除器
当我们使用shared_ptr去封装指针时,由于share_ptr的析构函数默认调用的是delete函数,所以只能对new和new[]成立,对于其他情况开辟的空间,比如以下两种情况:malloc和fopen,shared_ptr原本自带的析构函数,都不能很好的释放空间,甚至对于fopen甚至会报错,这时我们就需要针对不同的指针情况定制删除器。
Shared_ptr的底层也是支持传递定制删除器的,如上图所示,D del可以接收仿函数,上述不能正常析构的部分,加上下图析构函数
写完定制删除器后,在构建对象的时候传递,后面就可以正常析构了。