一、什么是智能指针?
智能指针是一个类,在这个类的构造函数中传入一个普通的指针,析构函数中释放这个指针。智能指针的类都是栈上的对象,所以当函数(或程序)结束时都会被自动释放。
借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好:
不需要显式地释放资源;
采用这种方式,对象所需的资源在其生命期内始终保持有。
二、为什么需要智能指针?
为了更好的解决以下两个问题所以出现了智能指针
1.当我们malloc出来的内存没有去释放时就会出现问题,就会存在内存泄漏问题;
2.异常安全问题。当在malloc和delete之间出现出现抛异常时,那么也会出现安全异常问题。
三、常见的智能指针
1、auto_ptr
采用管理权转移的方法,当拷贝构造一个对象时,将管理权交给新的对象,将原来的对象置空。
auto_ptr的模拟实现
template<typename T>
class SmartPtr
{
public:
SmartPtr(T* ptr = NULL) :mptr(ptr){}
SmartPtr(const SmartPtr& rhs) :mptr(rhs.mptr)//管理权唯一
//拷贝构造函数 要将本来指向这块内存的智能指针置为NULL
{
rhs.Release();
}
SmartPtr<T>& operator =(const SmartPtr<T>& rhs)
{
if(this != &rhs)
{
delete mptr;
mptr = rhs.mptr;
rhs.Release();
}
return *this;
}
//像指针一样
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
~SmartPtr()
{
delete mptr;
mptr = NULL;
}
private:
void const Release()
{
(T*)mptr = NULL;
}
T* mptr;
};
int main()
{
int *p = new int;
SmartPtr<int> sp1(p);
SmartPtr<int> sp2(sp1);//sp1对象指向0 sp2对象指向p
return 0;
}
出现的主要问题:如果拷贝出来的对象比原来的对象先调用析构函数,则原来的对象虽然为false,但却在访问一块已经释放的空间,原因在于拷贝对象的释放会导致原对象的ptr指向的内容跟着被释放。
2、scope_ptr
一块堆内存只有一个智能指针指向。具体实现是将拷贝构造函数和赋值运算符重载函数设置为保护或私有,并且只声明不实现。
scope_ptr的模拟实现
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>& rhs);
Scope_ptr<T>& operator=(const Scope_ptr<T>& rhs);
T* mptr;
}
scoped_ptr的实现和auto_ptr非常类似,不同的是 scoped_ptr有着更严格的使用限制——不能拷贝,这就意味着scoped_ptr 是不能转换其所有权的。
3、shared_ptr:强智能指针
通过引用计数实现多个shared_ptr对象之间共享资源,拷贝或赋值时将引用计数加1,析构时只有当引用计数减到0才释放空间,否则只需将引用计数减1即可。
class RefManage
{
public:
static RefManage* getInstance()
{
return &rm;
}
private://单例模式
static RefManage rm;//一个对象
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++;
length++;
}
else
{
arr[index].refcount++;
}
}
}
void delRef(void* ptr)//减少引用计数
{
if(ptr != NULL)
{
int index = find(ptr);
if(index < 0)
{
throw std::exception("addr is not exist");
}
else
{
if(getRef(ptr) != 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++)
{
if(arr[i].addr == ptr)
{
return i;
}
}
return -1;
}
class Node
{
public:
Node(void* ptr = NULL,int ref = 0)
:addr(ptr),refcount(ref){}
public:
void* addr;
int refcount;
};
Node arr[10];
int length;//数组元素的有效长度,当前可以插入的数组下标
};
RefManage RefManage::rm;//初始化唯一的对象
template<typename T>
class Shared_ptr
{
public:
Shared_ptr(T* ptr = NULL):mptr(ptr)
{
AddRef();
}
Shared_ptr(const Shared_ptr<T>& rhs):mptr(rhs.mptr)
{
AddRef();
}
Shared_ptr<T>& operator=(const Shared_ptr<T>& rhs)
{
if(this != &rhs)
{
DelRef();
if(GetRef() == 0)
{
delete mptr;
}
mptr = rhs.mptr;
AddRef();
}
}
T* GetPtr()const
{
return mptr;
}
~Shared_ptr()
{
DelRef();
if(GetRef() == 0)
{
delete mptr;
}
mptr = NULL;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
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* Shared_ptr<T>::rm = RefManage::getInstance();
出现的问题:
(1)在多线程环境下,引用计数的更新存在安全隐患;
方案:可以在改变引用计数的时候加上一把互斥锁,防止多线程带来的隐患。
(2)相互引用问题
class B;
class A
{
public:
shared_ptr<B>pb;weak_ptr<B>pb;
};
class B
{
public:
shared_ptr<A>pa;//weak_ptr<A>pa;
};
int main()
{
shared_ptr<A> spa(new A());
shared_ptr<B> spb(new B());
spa->pb = spb;
spb->pa = spa;
return 0;
}
现在两个节点都没有被释放!
原因:加了这两句代码后,这两个节点的引用计数都增加了1。出了作用域进行析构时,两个对象均不能释放,形成了循环引用,谁都释放不了。
4、weak-ptr
weak_ptr弱智能指针,虽然有引用计数,但实际上它并不增加计数,而是只观察对象的引用计数,所以不能单独使用。weak_ptr的引用计数指的是有多少个weak_ptr在观察同一个shared_ptr。
而shared_ptr强智能指针的引用计数是对资源的引用计数,所以此时对象A的引用计数只为1,对象B的引用计数也只为1。当主函数return返回后,对象A和B的引用计数减为0,在析构的时候释放内存,不会造成内存泄漏。
template<typaname 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 Shared_ptr<T>& rhs)
{
mptr = rhs.GetPtr;
return *this;
}
~Weak_ptr()
{
mptr = NULL;
}
T* operator*()
{
return *mptr;
}
T& operator->()
{
return mptr;
}
private:
T* mptr;
};
强弱智能指针的使用总结:
创建对象的时候用shared_ptr强智能指针,别的地方一律持有weak_ptr弱类型指针,否则析构顺序会出现错误。
当通过弱智能指针访问对象时,需要先进行lock提升操作,提升成功,证明对象还在,再通过强智能指针访问对象。