智能指针
前面我们一直在铺垫智能指针,这个词听上去非常高大上,但其实我们不需要害怕,就像一开始我们学什么封装继承多态,这些东西本质上就是对类和对象的补充
智能指针出现的背景其实为了是内存空间管理的问题,尤其是加了异常处理机制之后,就变得更为复杂
那我们其实就想,有没有一种东西,可以自动的控制内存资源
RAII思想
RAII思想说白了就是一种利用对象的声明周期来控制程序资源的简单技术,例如内存、文件句柄、网络连接等
在对象构造的时候,获取资源,在对象析构的时候,释放资源,让这个对象在生命周期之内,始终可以进行访问
这样做的本质其实是将这个资源托管给了对象,我们之前学类和对象的时候,他是可以自动调用构造和析构函数的,这就意味着我们不需要显示释放资源,并且可以让资源在其生命周期内始终有效,因为在调用析构的时候,才会释放资源
那更直白一点,所谓的智能指针本质上其实就是一个类,这个类可以管理资源
例如
template<class T>
class SmartPtr{
public:
SmartPtr(T* ptr = nullptr)
:_ptr(ptr)
{}
~SmartPtr()
{
if(_ptr != nullptr)
delete _ptr;
}
T& operator*() {return *_ptr;}
T* operator->() {return _ptr;}
private:
T* _ptr;
}
这里我们其实就实现了一个最简单的智能指针,并且他可以像指针一样使用了,可以解引用
智能指针的发展
在我们之前学习的时候提到一种拷贝构造的思想,引用计数,也称之为写时拷贝
根本思想是在拷贝的时候,我们只增加该这个对象的引用次数,析构的时候让引用次数减1即可,当有对这个新对象有写入操作的时候,引用次数减1,然后再拷贝出去
这里的思想其实差不多,因为智能指针在赋值的时候,本质其实是浅拷贝,那么当其中一个对象调用析构的时候,另一个对象就被动的释放了,就成了野指针
auto_ptr
这个auto_ptr是在C++98就出现了,而且他就存在上述的问题,并且这个巨大的bug至今没有被修复,主打一个补丁,后续推出了别的智能指针
因此一般公司里是禁用auto_ptr的
unique_ptr
这里的unique_ptr是独一无二指针的意思,是在C++11之中推出的,也就是说他禁止赋值和拷贝,这样做的话至少至少不会出现内存泄漏的大问题,就是用的不是很方便
至于unique_ptr是如何禁止赋值和拷贝的,一个是可以使用C++11的delete
关键字,更粗暴一点可以写个空的放在private里面
shared_ptr
这个shared_ptr几乎可以说是完美的了,而且对于之前的引用计数实现相对完善,我们也可以写一个简化版本的
shared_ptr的模拟实现
既然需要实现引用计数,那我们就需要增加一个int*成员变量来保存计数
template<class T>
class SmartPtr{
public:
SmartPtr(T* ptr = nullptr)
:_ptr(ptr)
,_pCount(new int(1))
{}
~SmartPtr()
{
Release();
}
SmartPtr<T>& operator=(const SmartPtr<T>& SP)
{
// 当被赋值的指针是新的资源时
if(_ptr != sp._ptr)
{
Rlease();
_ptr = sp._pCount();
AddCount();
}
return *this;
}
void Release()
{
if(--(*_pcount)==0) // 如果减1之后没有引用的对象则释放,否则只减1
{
delete _ptr;
delete _pCount;
}
}
void AddCount()
{
(*_pCount)++;
}
private:
T* _ptr;
int* _pCount;
}
我们这里没有实现解引用,但其实直接返回是一样的
这里的shared_ptr看起来非常完美了
但其实也有一点小的bug,就是当两个对象循环引用的时候,析构就会报错
这里的解决方案是使用weak_ptr,他的功能和shared_ptr很类似,但是他不会增加引用计数,我们稍后会进行补充
定制删除器
这个东西主要是为了解决一个问题,即当资源为数组类型的时候,我们调用的就不是delete,而是delete[]
例如
shared_ptr<int> sp(new int[10]);
C++11也考虑到了这个问题,因此我们在写构造函数的时候可以写一个定制的删除器,实际上传入一个仿函数或者lambda就可以了
shared_ptr<int> sp(new int[10],[](int* p) {delete[] p;});
关于weak_ptr的补充
weak_ptr是一种智能指针,但是一般不单独使用,只能和shared_ptr搭配使用,也就可以认为他是一种辅助工具,利用他可以看到shared_ptr的一些状态信息,例如有多少个指向相同的shared_ptr,指向的堆内存是否被释放等等
简单说其实,weak_ptr是可以从一个shared_ptr或者weak_ptr对象构造出来的,而他只有获得资源的观测权,并没有实际的共享资源,因此也没有增加引用计数,使用weak_ptr的use_count()成员还可以观测到资源的引用计数