智能指针基于RAII技术(Resource Acquisition Is Initialization,即资源获取即初始化,构造时分配资源,析构时释放资源)。
用指针构造智能指针对象,并在对象析构时自动释放指针指向的内存空间。
在智能指针对象中,重载了 * 和 -> 两个操作符,使其具有指针一样的行为。
C++的智能指针有以下四种:
template
class auto_ptr
{
public:
auto_ptr(T* ptr)
: _ptr(ptr)
{}
auto_ptr(auto_ptr& ap)
{
_ptr = ap._ptr;
ap._ptr = nullptr;
}
auto_ptr& operator=(auto_ptr& ap)
{
if (this != &ap)
{
delete _ptr;
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~auto_ptr()
{
delete _ptr;
}
private:
T* _ptr;
};
unique_ptr在auto_ptr的基础上直接禁用了拷贝构造和赋值重载(delete),从而避免了auto_ptr在使用上容易出错的问题,但是没有了这两个函数,对于用户非常不便。
基于写时拷贝技术,通过计数器的加减判断是否需要释放指针指向的空间。
// shared_ptr模拟实现
template
class shared_ptr
{
public:
shared_ptr(T* ptr)
: _ptr(ptr)
, _pCount(new int(1))
{}
shared_ptr(const shared_ptr& sp)
{
_ptr = sp._ptr;
_pCount = sp._pCount;
++* _pCount;
}
shared_ptr& operator=(const shared_ptr& sp)
{
if (_ptr != sp._ptr)
{
if (--(*_pCount) == 0)
{
delete _ptr;
delete _pCount;
}
_ptr = sp._ptr;
_pCount = sp._pCount;
++* _pCount;
}
return *this;
}
~shared_ptr()
{
if (--(*_pCount) == 0 && _ptr != nullptr)
{
delete _ptr;
delete _pCount;
}
}
private:
T* _ptr;
int* _pCount; // 计数器指针
};
weak_ptr
delete
进行指针指向空间的释放,但是如果指针是new []
或者malloc
出来的,那么使用delete就会出现问题。解决办法:在构造智能指针时可以提供删除器Deleter(仿函数),那么析构函数会自动调用该删除器进行资源回收。注:weak_ptr拷贝shared_ptr时,不会导致share_ptr的计数器增加,从而解决循环引用的问题。
引用计数有一个问题就是互相引用形成环(环形引用),这样两个指针指向的内存都无法释放。需要使用weak_ptr打破环形引用。
weak_ptr是一个弱引用,它是为了配合shared_ptr而引入的一种智能指针,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是说,它只引用,不计数。
如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放。所以weak_ptr不保证它指向的内存一定是有效的,在使用之前使用函数lock()检查weak_ptr是否为空指针。
在底层实现中,每个
std::shared_ptr
和std::weak_ptr
都有一个关联的控制块,这个控制块包含两个计数器:一个是共享所有权的强引用计数器(由std::shared_ptr
持有),另一个是弱引用计数器(由std::weak_ptr
持有)。弱引用计数器可以方便weak_ptr知道自己指向的shared_ptr是否已被销毁。
当最后一个
std::shared_ptr
被销毁时,它会删除所指向的对象,但如果还有std::weak_ptr
指向该对象,则控制块将保持不变。当最后一个指向对象的std::weak_ptr
被销毁时,它将清理控制块。