一、为什么要引入智能指针?
先来看一个例子:
#include <iostream>
using namespace std;
void func(int *ptr)
{
int *p=new int;
if(ptr==NULL)
{
throw exception("ptr is null");
}
*p=*ptr;
delete p;
}
int main()
{
func(NULL);
return 0;
}
分析:指针p指向一块堆内存,当func函数传入的参数ptr为空时,直接抛出异常,异常处理机制去处理异常,那么在func函数中p指向的这块堆内存就无法被释放,这时就出现了内存泄露。在java中系统有自带的垃圾回收机制,C++中系统没有自带的垃圾回收机制,因此引入智能指针,相当于C++中的垃圾回收机制。
使用auto_ptr智能指针将以上以上代码修改如下:
void func(int *ptr)
{
auto_ptr<int> p(new int);
if(ptr == NULL)
{
throw exception("ptr is null");
}
*p=*ptr;
}
二、智能指针的实现原理
栈上的内存由系统开辟,系统释放,堆上的内存是由程序人员手动开辟,手动释放,如果能实现程序人员手动开辟,系统释放的话,就不用担心内存泄露问题了。具体实现为在堆上开辟一块内存,在栈上申请一个对象,让栈上的对象指向这个堆内存,也就是说让这个对象管理这块堆内存,当对象的生命周期到了之后,就要销毁对象,根据栈上的特点,内存是由系统开辟,系统释放,就要释放这个对象所占的空间,对象调用析构函数释放空间,将堆的销毁放到这个析构函数中,这样就实现了堆上的内存手动开辟,系统释放,这就是智能指针的思想,智能指针以对象的方式存在但具有指针的功能。
三、智能指针的分类
在C11标准之前,标准库中只有auto_ptr一个智能指针,由于auto_ptr存在很大的缺陷,所以在C11标准之后,将auto_ptr智能指针摒弃了,引入了scope_ptr,shared_ptr和weak_ptr这三个智能指针。
(1)auto_ptr
auto_ptr:管理权唯一,释放权唯一,不允许有多个智能指针指向同一块堆内存,只允许一个智能指针指向一块堆内存。
#include <iostream>
template<typename T>
class Auto_ptr
{
public:
Auto_ptr(T *ptr = NULL) : mptr(ptr) {} //构造函数
Auto_ptr(const Auto_ptr& rhs) : mptr(rhs.mptr) //拷贝构造函数
{
rhs.Release();
}
Auto_ptr<T>& operator =(const Auto_ptr<T>& rhs) //赋值运算符重载函数
{
if(this != &rhs) //自赋值
{
delete mptr;
mptr = rhs.mptr;
rhs.Release();
}
return *this;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
~Auto_ptr()
{
delete mptr;
mptr = NULL;
}
private:
T *mptr;
void Release() const
{
(T *)mptr = NULL;
}
};
int main()
{
int *p=new int;
Auto_ptr<int> sp1(p);
Auto_ptr<int> sp2(sp1);
sp1 = sp2;
*sp1 = 10;
return 0;
}
缺点:如果有多个智能指针指向同一块堆内存,只有一个智能指针有管理权,其他智能指针将置为NULL,这会导致其他智能指针失效,使用没有管理权的智能指针进行操作,会导致程序奔溃。
带有标志位的智能指针: 管理权不唯一,释放权唯一,标志位flag代表智能指针的释放权限,释放权转移时应遵循,旧的智能指针有释放权,新的智能指针就有释放权,旧的智能指针没有释放权,新的智能指针就没有释放权。
#include <iostream>
template<typename T>
class Smart_ptr
{
public:
Smart_ptr(T *ptr):mptr(ptr)
{
flag=true;
}
Smart_ptr(const Smart_ptr<T>&rhs):mptr(rhs.mptr)
{
flag=rhs.flag;
rhs.flag=false;
}
Smart_ptr<T>& operator=(const Smart_ptr<T>& rhs)//赋值运算符重载
{
if (this != &rhs)//不是自复制
{
~Smart_ptr();
mptr=rhs.mptr;
flag=rhs.flag;
rhs.flag=false;
}
return *this;
}
~Smart_ptr()
{
if (flag)
{
delete mptr;
}
mptr = NULL;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
private:
T *mptr;
mutable bool flag;//去除常性
};
int main()
{
Smart_ptr<int> sp1(new int);
Smart_ptr<int> sp2(sp1);
Smart_ptr<int> sp3(sp1);
*sp1=20;
return 0;
}
缺点:释放权的转移,导致堆内存被提前释放,导致其他智能指针出现错误,如下例所示:
void func(Smart_ptr<int> sp)
{}
int main()
{
Smart_ptr<int> sp1(new int);
Smart_ptr<int> sp2(sp1);
Smart_ptr<int> sp3(sp1);
func(sp2);
*sp1=20;
return 0;
}
分析:func函数传参时,将有释放权的sp2传递给sp,sp调用构造函数初始化,sp就有了释放权,当形参对象sp销毁时,sp所指向的堆内存就会被释放,这个堆内存就被提前释放了,所以带有标志位的智能指针的缺点是释放权的转移,导致堆内存被提前释放,导致其他智能指针出现错误。
上面这两种智能指针都是因为释放权的转移导致出现问题,即拷贝构造或者赋值运算符重载时,让两个智能指针对象或多个智能指针对象指向同一块堆内存引发的问题。
(2)scope_ptr
scope_ptr:管理权唯一,释放权唯一,一个智能指针只能引用一块堆内存,不允许多个智能指针指向同一块堆内存,实现时将拷贝构造函数和赋值运算符重载函数都声明为私有的,拒绝所有权的转让。
#include <iostream>
template<typename T>
class Scope_Ptr
{
public:
Scope_Ptr(T* ptr) :mptr(ptr){}
~Scope_Ptr()
{
delete mptr;
mptr=NULL;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
private:
Scope_Ptr(const Scope_Ptr<T>&);//不需要对内的调用,声明就好
Scope_Ptr<T>& operator=(const Scope_Ptr<T>&);
T* mptr;
};
int main()
{
int* p = new int;
Scope_Ptr<int> sp1(p);
return 0;
}
缺点:在外部人为地将多个智能指针指向同一块堆内存,这几个智能指针都是有释放权的,当其中一个智能指针将指向的堆内存释放,其余的智能指针就成了野指针,不能在对该堆内存进行操作。
(3)shared_ptr
shared_ptr(强智能指针):shared_ptr是带有引用计数的智能指针,引用计数表示有多少个智能指针对象管理这个堆内存。
可设计为两个类, 引用计数管理器类和智能指针类
引用计数管理器类:其结点分为两个域,堆内存地址域和引用计数域, 引用计数管理器类只会对引用计数进行管理,不做堆内存的释放,可将用计数管理器类设置为单例模式。
addRef():添加引用计数,首先判断智能指针指向的堆内存地址是否存在,若存在,引用计数加1,若不存在,插入一个新结点,将该堆内存地址添加到引用计数管理器类中,引用计数置为1。
delRef():减少引用计数,首先判断智能指针指向的堆内存地址是否存在,若存在,引用计数减1,若不存在,抛出异常。
getRef():获取引用计数的个数,首先判断智能指针指向的堆内存地址是否存在,若存在,返回引用计数域,若不存在,返回-1。
智能指针类:做堆内存的释放,若智能指针指向的堆内存的引用计数域为0,释放堆内存。
#include <iostream>
class RefManage
{
public:
static RefManage* getInstance()//静态函数不依赖于对象的调用,不能设计为普通的成员方法,提供一个接口,生成对象
{
return &rm;//返回值不能是类对象类型,类对象类型有临时对象生成
}
private:
RefManage():length(0)
{}
RefManage(const RefManage& rhs);
public:
void addRef(void* ptr)//判空
{
if (ptr != NULL)
{
int index = Find(ptr);//查询地址是否存在
if (index < 0)//没找到
{
//arr[length].addr = ptr; //插入一个新结点
//arr[length++].refcount++;
Node tmp(ptr, 1);
arr[length++] = tmp;
}
else//找到了
{
arr[index].refcount++;
}
}
}
void delRef(void* ptr)
{
if (ptr != NULL)
{
int index = Find(ptr);//查询地址是否存在
if (index < 0)//没找到
{
throw exception("addr is not exist");
}
else
{
if (arr[index].refcount != 0)//找到了
{
arr[index].refcount--;
}
}
}
}
int getRef(void* ptr)
{
if (ptr == NULL)
{
return 0;
}
int index = Find(ptr);
if (index < 0)//没找到
{
return -1;
}
else//找到了
{
return arr[index].refcount;
}
}
private:
int Find(void* ptr)//查询地址下标
{
for (int i = 0; i < length; ++i)//给一个变量length存放有效结点个数 当前要插入的结点下标
{
if (arr[i].addr == ptr)
{
return i;
}
}
return -1;
}
class Node
{
public:
Node(void* ptr, int ref = 0)
{
addr = ptr;
refcount = ref;
}
Node()
{
memset(this, 0, sizeof(Node));//sizeof(Node)=8
}
public:
void* addr;//地址
int refcount;//个数
};
Node arr[10];
int length;//有效节点个数,即当前要插入的节点下标
static RefManage rm;
};
RefManage RefManage::rm;//在main函数调用之前就生成 .data
template<typename T>
class Shard_Ptr
{
public:
Shard_Ptr(T* ptr=NULL) :mptr(ptr)
{
AddRef();
}
Shard_Ptr(const Shard_Ptr<T>& rhs):mptr(rhs.mptr)
{
AddRef();
}
Shard_Ptr<T>& operator=(const Shard_Ptr<T>& rhs)
{
if (this != &rhs)//自复制
{
DelRef();
if (GetRef() == 0)
{
delete mptr;//释放旧资源
}
mptr = rhs.mptr;//新资源开辟
AddRef();
}
return *this;
}
~Shard_Ptr()
{
DelRef();
if (GetRef() == 0)
{
delete mptr;
}
mptr = NULL;
}
T* operator->()
{
return mptr;
}
T* operator*()
{
return *mptr;
}
T* GetPtr()const
{
return mptr;
}
private:
void AddRef()
{
rm->addRef(mptr);
}
void DelRef()
{
rm->delRef(mptr);
}
int GetRef()
{
return rm->getRef(mptr);
}
T* mptr;
static RefManage* rm;
};
template<typename T>
RefManage* Shard_Ptr<T>::rm = RefManage::getInstance();//静态成员变量一定要在类外初始化
int main()
{
int *p=new int;
Shard_Ptr<int> sp1(p);
Shard_Ptr<int> sp2(p);
Shard_Ptr<int> sp3(p);
Shard_Ptr<int> sp4(p);
return 0;
}
缺点:强智能指针相互引用会导致对象无法销毁,如下例所示:
class B;
class A
{
public:
A()
{
cout<<"A::A()"<<endl;
}
~A()
{
cout<<"A::~A()"<<endl;
}
Shard_Ptr<B> pa;
};
class B
{
public:
B()
{
cout<<"B::B()"<<endl;
}
~B()
{
cout<<"~B::B()"<<endl;
}
Shard_Ptr<A> pb;
};
int main()
{
Shard_Ptr<A> spa(new A());
Shard_Ptr<B> spb(new B());
spa->pa=spb;
spb->pb=spa;
return 0;
}
运行结果如下:
分析:由运行结果可看出,对象A和B没有调用析构函数释放内存,出现内存泄露,下图对主函数中的4条语句进行了分析,两个强智能指针相互引用,两个强智能指针都会对堆内存的引用计数加1,而销毁时,只会对堆内存的引用计数减一次,引用计数不为0,所以不会对堆内存进行释放,这样就出现了内存泄露。
(4)weak_ptr
weak_ptr(弱智能指针):弱智能指针只对堆内存进行管理,不添加引用计数,堆内存的释放交给强智能指针来做,弱智能指针搭配强智能指针使用,解决强智能指针相互引用导致内存泄漏的问题。
#include <iostream>
using namespace std;
template<typename T>
class Weak_Ptr
{
public:
Weak_Ptr(T* ptr =NULL):mptr(ptr)
{}
Weak_Ptr(const Weak_Ptr<T>& rhs):mptr(rhs.mptr)
{}
Weak_Ptr<T>& operator=(const Weak_Ptr<T>& rhs)
{
if(this != &rhs)
{
mptr=rhs.mptr;//建立管理关系
}
return *this;
}
Weak_Ptr<T>& operator=(const Shard_Ptr<T>& rhs)
{
mptr=rhs.GetPtr();
return *this;
}
T* operator->()
{
return mptr;
}
T* operator*()
{
return *mptr;
}
~Weak_Ptr()
{}
private:
T* mptr;
};
class B;
class A
{
public:
A()
{
cout<<"A::A()"<<endl;
}
~A()
{
cout<<"A::~A()"<<endl;
}
Weak_Ptr<B> pa;
};
class B
{
public:
B()
{
cout<<"B::B()"<<endl;
}
~B()
{
cout<<"B::~B()"<<endl;
}
Weak_Ptr<A> pb;
};
int main()
{
Shard_Ptr<A> spa(new A());
Shard_Ptr<B> spb(new B());
spa->pa=spb;
spb->pb=spa;
return 0;
}
运行结果如下:
缺点:弱智能指针不能单个使用,因为弱智能指针的析构函数没有delete操作,单独使用会导致内存泄露。