智能指针
为什么需要有智能指针?
- 智能指针是一种预防型的内存泄漏的解决方案。智能指针在C++没有垃圾回收器环境下,保证在任何情况下资源都能够进行合理的释放,而不用在所有可能退出的地方都进行是否释放的检测,避免由此引发的资源泄露问题;
原理
RAII:利用对象的生命周期来控制程序资源;将资源使用类的方式进行封装:在对象构造时获取资源,在析构函数中清理资源
- 不需要显式释放资源;
- 采用这种方式对象所需的资源在其生命周期内始终保持有效;
智能指针原理:
- RAII(资源可以自动释放)+operator*/operator->(当成指针使用)+解决浅拷贝的问题
- 浅拷贝的后果:多个对象共享同一份资源,在销毁对象时,同一份资源被释放多次导致代码崩溃
C++98-auto_ptr:
- 版本一:进行资源转移:拷贝与赋值运算符重载意味着资源的转移,在得到资源后,之前的资源所有者断开连接;多个对象存在拷贝转移,无法同时访问资源;具有独占性;
- 版本二:进行资源管理权转移:加入_owner变量表示资源拥有者,资源拥有者才可以释放,但其他对象都可以使用资源;拷贝与赋值运算符重载意味着资源所有权的转移,有可能造成野指针;资源拥有着释放后,其他对象依然有可能操作,变成野指针操作;
ps:后来又从版本2还原为版本1,因为野指针伤不起啊o(╥﹏╥)o; - 建议使用场景:鉴于上述方法存在明显的缺陷,尽量不使用auto_ptr;
C++11-unique_ptr:单对象独占资源(唯一指针)
- 为了替换不安全的auto_ptr,从源头上制止了资源转转移后访问失效的问题;还可以自定义删除操作/作为容器元素/管理动态数组;
- 禁止调用拷贝构造,赋值运算符重载的方式共享资源;禁止方式:私有域声明不定义(防止类外自行定义,防止友元函数)/删除函数;
- 建议使用场景:一份资源被一个对象独占不分享的情况下可以使用;
C++11-shared_ptr:共享智能指针
-
为了解决前两者资源独占不能共享而产生;
-
解决浅拷贝问题:使用引用计数的方式来解决浅拷贝问题并实现共享资源
在内部为每个资源维护一份计数,表示被几个对象共享;
在引用资源的对象销毁时,说明该对象不再使用资源,资源的引用计数减一;
如果引用计数是0,说明需要释放该资源了,如果不是,则不释放,防止其他正在使用的对象出现野指针; -
解决管理任意类型指针的问题:仿函数的方式定制删除器:
资源可以malloc,new,文件指针,套接字,这些的释放方式都不同,要让用户根据需要自行选择 -
解决线程安全问题:
智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁的,也就是说引用计数的操作得是线程安全的。
智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。 -
解决循环引用的问题:
当我们直接使用shared_ptr来管理双向链表的资源,就会出现节点的资源泄露的问题:
#include<memory>
#include<iostream>
struct ListNode {
public:
ListNode ()
:_data (0) , _pPre (nullptr) , _pNext (nullptr)
{ }
~ListNode () { std::cout << "~ListNode()" << std::endl; };
int _data;
std::shared_ptr<ListNode> _pPre;
std::shared_ptr<ListNode> _pNext;
};
int main () {
std::shared_ptr<ListNode> node1 (new ListNode);
std::shared_ptr<ListNode> node2 (new ListNode);
std::cout << node1.use_count () << std::endl;
std::cout << node2.use_count () << std::endl;
node1->_pNext = node2;
node2->_pPre = node1;//此处成环
std::cout << node1.use_count () << std::endl;
std::cout << node2.use_count () << std::endl;
return 0;
}
我们来用图说明问题发生的过程:
- sp1和sp2两个智能指针分别指向node1,node2,引用计数此时各为一;
- node1的next指向node2,node2的pre指向node1,此时各自的引用计数为2;
- 当sp2执行析构时,引用计数-1还剩1,所以sp2不释放node2资源直接退出;
- 同理当sp1执行析构时,不释放node1直接退出;
- 此时sp1,sp2都已经退出,但node1,node2一个都没有释放,资源泄露,这就是循环引用问题;
- 解决方法:
在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
C++11-weak_ptr:弱指针:专门为了打破循环引,和shared_ptr配合使用
- 可以从一个shared_ptr或者另一个weak_ptr对象构造而来。
- weak_ptr是为了配合shared_ptr而引入的一种智能指针,它更像是shared_ptr的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载operator*和->,因此取名为weak,表明其是功能较弱的智能指针。
- 协助shared_ptr工作,可获得资源的观测权,像旁观者那样观测资源的使用情况。观察者意味着weak_ptr只对shared_ptr 进行引用,而不改变其引用计数,当被观察的shared_ptr失效后,相应的weak_ptr也相应失效。
使用了weak_ptr后,上面例子的过程就会变成:
- sp1和sp2两个智能指针分别指向node1,node2,因为使用了weak_ptr,use对应shared_ptr的引用个数,weak对应weak_ptr的引用个数,创建时的初始值都为1;
- node1的next指向node2,node2的pre指向node1,因为next和pre都是weak_ptr,所以各自的weak引用计数+1,use不变;
- 当sp2执行析构时,use-1为0,说明之后将没有shared_ptr类型的对象在用node2了,之中依次执行next和pre这两个weak_ptr的析构,node2的_pNext为空,直接调用其析构,_pPre的计数器(sp1的weak)减1后不为0,_pPre释放但其指向的node1资源不释放,此时node2释放完毕,sp2的weak-1
- 同理当sp1执行析构时,use-1=0,之中执行_pNext的析构,其计数器(sp2的weak-1),此时sp2的weak为0,use为0,sp2释放,_pPre为空,直接析构,node1释放完毕,sp1的weak-1,此时sp1weak=0,use=0,sp1释放;
- sp1,sp2都已经释放,node1,node2也已经释放,循环引用问题被打破;
ps:weak_ptr将观测者和所有者区分开来,观测者观测资源不增加shared_ptr的计数,而是增加weak_ptr,使得所有者调用析构时,就一定可以释放资源,最终打破循环引用问题,将双锁问题转化为单锁从而避免类死锁现象;
自行实现
auto_ptr
template<class T>
class AutoPtr {
public:
AutoPtr (T* ptr = NULL)
: _ptr (ptr) {
}
~AutoPtr () {
if ( _ptr )
delete _ptr;
}
// 一旦发生拷贝,就将ap中资源转移到当前对象中,然后另ap与其所管理资源断开联系,
// 这样就解决了一块空间被多个对象使用而造成程序奔溃问题
AutoPtr (AutoPtr<T>& ap)
: _ptr (ap._ptr) {
ap._ptr = NULL;
}
AutoPtr<T>& operator=(AutoPtr<T>& ap) {
// 检测是否为自己给自己赋值
if ( this != &ap ) {
// 释放当前对象中资源
if ( _ptr )
delete _ptr;
// 转移ap中资源到当前对象中
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private: T* _ptr;
};
int main () {
AutoPtr<int> ap (new int);
// 现在再从实现原理层来分析会发现,这里拷贝后把ap对象的指针赋空了,导致ap对象悬空
// 通过ap对象访问资源时就会出现问题。
AutoPtr<int> copy (ap);
*ap= 10;
return 0;
}
unique_ptr
#include<fstream>
#include<iostream>
namespace Delector {//删除器
template<class T>
struct DFDel {//默认是new用delete删除
void operator()(T*& ptr) {
if ( ptr ) {
delete ptr;
ptr = nullptr;
}
}
};
template<class T>
struct Free {//处理maloc的资源
void operator()(T*& ptr) {
if ( ptr ) {
free (ptr);
ptr = nullptr;
}
}
};
struct FClose {
void operator()(FILE*& pf) {//处理文件流指针和套接字;
if ( pf ) {
fclose (pf);
pf = nullptr;
}
}
};
}
template<class T , class DE = Delector::DFDel<T>>
class Unique_ptr {
public:
Unique_ptr (T* p = nullptr)
:ptr (p) {
}
~Unique_ptr{
DE (ptr);
}
T& operator*() {
return *ptr;
}
T* operator->() {
return ptr;
}
private:
Unique_ptr (const Unique_ptr<T,DE>& s) = delete;
Unique_ptr& operator=(const Unique_ptr<T,DE>&) = delete;
private:
T* ptr;
};
shared_ptr
#include<iostream>
#include<fstream>
#include<mutex>
namespace Delector {//删除器
template<class T>
struct DFDel {//默认是new用delete删除
void operator()(T*& ptr) {//仿函数(重载())实现不同的释放资源的操作
if ( ptr ) {
delete ptr;
ptr = nullptr;
}
}
};
template<class T>
struct Free {//处理maloc的资源
void operator()(T*& ptr) {
if ( ptr ) {
free (ptr);
ptr = nullptr;
}
}
};
struct FClose {
void operator()(FILE*& pf) {//处理文件流指针和套接字;
if ( pf ) {
fclose (pf);
pf = nullptr;
}
}
};
}
template<class T , class DF = Delector::DFDel<T>>
class SharedPtr {
public:
SharedPtr (T* ptr = nullptr)
: _ptr (ptr)
, _pCount (nullptr)
, _pMutex (nullptr) {
if ( _ptr ) {
_pMutex = new std::mutex;
_pCount = new int (1);
}
}
SharedPtr (const SharedPtr<T,DF>& sp)
: _ptr (sp._ptr)
, _pCount (sp._pCount)
, _pMutex (sp._pMutex) {
AddRef ();
}
~SharedPtr () { Release (); }
SharedPtr<T,DF>& operator=(const SharedPtr<T,DF>& sp) {
if ( this != &sp ) {
// 释放管理的旧资源
Release ();
// 共享管理新对象的资源,并增加引用计数
_ptr = sp._ptr;
_pCount = sp._pCount;
AddRef ();
}
return *this;
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
int UseCount () { return *_pCount; }
T* Get () { return _ptr; }
private:
void AddRef () {//线程安全的++操作
_pMutex->lock ();
++*_pCount;
_pMutex->unlock ();
}
int SubRef () {//线程安全的--操作
_pMutex->lock ();
--*_pCount;
_pMutex->unlock ();
return *_pCount;
}
void Release () {//删除函数
if ( _ptr && 0 == SubRef () ) {
DF ()(_ptr);
delete _pCount;
if ( _pMutex )
delete _pMutex;
}
}
private:
int* _pCount; // 引用计数
T* _ptr; // 指向管理资源的指针
std::mutex * _pMutex;//加锁线程安全
};
int main () {
int * a = (int *)malloc (100 * sizeof (int));
SharedPtr<int , Delector::Free<int>> test (a);
return 0;
}