智能指针
智能指针用于管理在堆中申请的内存,允许使用者不需要显式调用delete释放内存,属于自动内存管理的一种实现。
智能指针的存在,主要是为了解决动态分配对象的释放问题。实践证明,动态分配对象的正确释放是编程中非常容易出错的地方。而智能指针的出现,为我们提供了一种解决方案,让动态分配对象不再需要时能够自动释放。智能指针的工作主要依靠引用计数与资源获取即初始化这两种技术实现。
资源获取即初始化
资源获取即初始化,是一种常用的管理资源方法。其做法是使用一个对象,在其构造时获取资源,在对象的生命周期内,对资源的访问将始终保持有效,当对象析构的时候释放资源。
由于C++标准保证在任何情况下,对象在其生命周期结束时都将销毁,其析构函数最终会被调用。所以在栈上创建一个对象,对象在其构造函数调用时获得资源,在析构函数调用时释放占用的资源。此时无论过程的执行中是产生异常或提前返回,由于栈对象的生命周期也随之结束,对象的析构函数会被调用,占用的资源将被正确释放。简化了代码编写,不再需要为所有的过程返回编写释放资源的代码。同时也解决了由于出现未被捕捉异常而导致的资源无法正确释放产生的泄露问题。
引用计数
引用计数是用于统计对象的使用情况的一种技术,每一个对象负责维护自身所有引用的计数值。当一个新的引用指向对象时,视作引用计数将会递增,当一个引用被去掉时,引用计数便递减。当引用计数为零时,将释放对象所占用的资源。
简单智能指针代码解析
以下为一个简单的智能指针示例,主要为了展示引用计数与资源获取即初始化在智能指针中的应用。
template<class T>
class CSmartPtr
{
public:
// 无参数构造函数,由于并没有绑定动态分配对象,所以初始化为空指针状态
CSmartPtr()
: resource(0)
, refcnt(0)
{
}
// 接受一个参数的构造函数,参数p指向需要智能指针进行管理的动态分配对象。
// 智能指针为其初始化引用计数对象,并把计数值设置为1。因为智能指针实例假
// 设参数p指向的对象是交由它进行管理,故同一个对象不应用于初始化多个智能
// 指针实例。
explicit CSmartPtr(T *p)
: resource(p)
{
refcnt = new long();
*refcnt = 1;
}
// 析构函数,当触发析构函数时,表示不再需要此智能指针实例,此时应该对引用
// 计数值减一,代表一个引用被去掉。
~CSmartPtr()
{
if (!refcnt)
{
return;
}
// 由于管理同一个资源的智能指针可能存在于不同的线程,为了线程安全,此处
// 应使用平台提供的原子函数对引用计数对象进行递减的操作。
long count = ::InterlockedDecrement(refcnt);
if (count > 0)
{
return;
}
// 若引用计数为0,代表此智能指针对象销毁后,不再需要当前正在管理的资
// 源。此时应销毁管理的资源与引用计数对象。
delete resource;
delete refcnt;
}
// 拷贝构造函数,接受另一个智能指针实例,代表要新增一个引用去引用参数r智能
// 指针管理的资源
CSmartPtr(const CSmartPtr<T> &r)// 拷贝构造
{
resource = r.resource;
refcnt = r.refcnt;
if (refcnt)
{
// 由于管理同一个资源的智能指针可能存在于不同的线程,为了线程安全,
// 此处应使用平台提供的原子函数对引用计数对象进行递增的操作。
::InterlockedIncrement(refcnt);
}
}
// 赋值操作,接受另一个智能指针实例,代表要新增一个引用去引用参数r智能
// 指针管理的资源
CSmartPtr<T>& operator=(const CSmartPtr<T>& r)
{
// 利用拷贝构造函数构建临时对象保存当前智能指针,以利用析构函数操作引用
// 计数
CSmartPtr<T> tmp(*this);
resource = r.resource;
if (refcnt)
{
// 由于对象原本管理着资源,所以此处为计数值减一移除一个引用
::InterlockedDecrement(refcnt);
}
refcnt = r.refcnt;
// 设置新的引用计数对象,并为新资源的计数值加一
::InterlockedIncrement(refcnt);
return *this;
}
// 重载操作符,使智能指针具有指针通过*运算符获取指针指向对象实例的能力
T& operator*() const
{
return *resource;
}
// 重载操作符,使智能指针具有直接操作指针指向对象的成员的能力
T* operator->() const
{
return resource;
}
// 获取智能指针管理资源的原始指针
T* get() const
{
return resource;
}
// 重载显式bool类型转换,使智能指针能够在布尔值判断时,空指针被识别为false
explicit operator bool() const
{
return *refcnt == 0;
}
private:
// 保存智能指针管理的资源
T* resource;
// 保存引用计数对象
long* refcnt;
};
C++11的智能指针
C++11中存在着以下的智能指针,auto_ptr、unique_ptr、shared_ptr与weak_ptr。除了auto_ptr是C++03标准加入,其余皆是C++11标准开始加入。
类型 | 说明 |
---|---|
auto_ptr | 由其在拷贝时的行为无法满足STL容器,不被建议使用 |
unique_ptr | 代表唯一的资源,此智能指针不允许拷贝 |
shared_ptr | 使用引用计数,允许拷贝,行为最像原始指针 |
weak_ptr | 配合shared_ptr使用,用于观察shared_ptr,可以在特定时候获得对应的shared_ptr实例 |
在编译器允许的情况下,需要使用智能指针时,应该尽量使用C++11添加的unique_ptr、shared_ptr与weak_ptr,而非自己实现一个新智能指针。因为标准库中的智能指针已经经过长时间的实践,比起个人实现考虑得更周全,与STL的结合也更好。
参考文献
《Boost程序库完全开发指南(第3版)》与《C++ Primer(第5版)》