RAII
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源的技术
- 不需要显示的释放资源
- 对象的资源在其生命周期类保持有效
通常控制的资源:动态申请的内存、文件描述符、互斥量、网络连接等
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象 。
智能指针原理
-
RAII特性
-
具有像指针一样的行为
(重载
operator*()
和operator->()
)
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
{}
//RAII特性
~SmartPtr()
{
if (_ptr)
{
delete _ptr;
}
}
//具有像指针一样的行为
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
指针允许拷贝和赋值,使多个指针对象指向同一块资源,即浅拷贝。如果使用上述代码的SmartPtr
,并且进行过拷贝赋值,那么很可能会导致野指针问题。这样的话对于SmartPtr
的重点就在解决:
- 智能指针对象的拷贝问题
auto_ptr
C++98中引入的智能指针
通过管理权转移的方式解决智能指针的拷贝问题,保证一个资源在任何时刻都只有一个对象在对其进行管理,这时同一个资源就不会被多次释放了
- 拷贝构造函数中,用传入对象来初始化当前对象,并将传入对象管理资源的指针置空
- 赋值运算符重载中,先将当前管理的资源释放,然后再接管传入对象管理的资源,并将传入对象管理资源的指针置空
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr = nullptr)
: _ptr(ptr)
{}
~auto_ptr()
{
if (_ptr)
{
cout << "Delete:" << _ptr << endl;
delete _ptr;
}
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
//智能指针对象的拷贝问题
// 拷贝构造函数
auto_ptr(auto_ptr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr = nullptr;
}
// 赋值运算重载
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
if (this != &ap)//防止自己给自己赋值的操作
{
if (_ptr)//释放原来管理的资源
{
delete _ptr;
}
//管理权转移
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
private:
T* _ptr;
};
将一个auto_ptr对象赋值给另一个对象,原本的auto_ptr对象将拥有NULL指针,此时出现“悬垂指针”问题,比如:继续使用原对象,导致空指针解引用等问题。
由于这个缺陷,导致auto_ptr成为一种过时的智能指针,而后C++11中有更加安全可靠的选择。
unique_ptr
C++11中引入的智能指针
通过简单粗暴的防止对智能指针对象进行拷贝,这样也能保证资源不会被多次释放
- 拷贝构造函数和赋值运算符被显式删除
template<class T>
class unique_ptr
{
// 防拷贝 C++98 只声明不实现
//private:
//unique_ptr(unique_ptr<T>& ap);
//unique_ptr<T>& operator=(unique_ptr<T>& ap);
public:
unique_ptr(T* ptr = nullptr)
: _ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
{
cout << "Delete:" << _ptr << endl;
delete _ptr;
}
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
// 防拷贝 C++11
unique_ptr(unique_ptr<T>& ap) = delete;
unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
private:
T* _ptr;
};
shared_ptr
C++11引入的更安全可靠的智能指针
通过引用计数的方式来实现多个shared_ptr对象之间共享资源
- 每个被管理的资源都有一个引用计数,初始化为1
- 当新对象管理该资源时,引用计数+1,当某对象不再管理该资源时,引用计数-1
- 当该引用计数为0时,会将该资源释放
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _pcount(new int(1))
{}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
//析构后不再管理该资源
~shared_ptr()
{
if (--(*_pcount) == 0)
{
if (_ptr != nullptr)
{
delete _ptr;
_ptr = nullptr;
}
delete _pcount;
_pcount = nullptr;
}
}
//拷贝构造函数
shared_ptr(shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
{
(*_pcount)++;
}
//赋值,不再管理原资源
shared_ptr& operator=(shared_ptr<T>& sp)
{
if (_ptr != sp._ptr) //避免管理同资源的对象间赋值
{
if (--(*_pcount) == 0) //引用计数--
{
delete _ptr;
delete _pcount;
}
//管理同一块资源
_ptr = sp._ptr;
_pcount = sp._pcount;
(*_pcount)++; //引用计数++
}
return *this;
}
private:
T* _ptr; //管理的资源
int* _pcount; //管理的资源对应的引用计数
};
引用计数使用动态申请的空间
引用计数本身也是智能指针所管理/共享的资源,使用指针类型可以进行管理/共享。
如果使用int型变量,管理同一个资源时无法很好保证引用计数的一致性;如果使用static变量,同类的实例化对象其实是共享该静态成员的,即使管理的不是同一个资源。
引用计数++/- -操作的线程安全问题
可以使用mutex* _pmutex
互斥锁来对引用计数进行加锁操作,同样的互斥锁也使用指针类型。
定制删除器
share_ptr
并不是只管理以new
方式申请到的内存空间,也可能是以new[]
的方式申请到的空间,或管理的是一个文件指针。此时就需要使用特定的删除器
template<class T>
struct Delete
{
void operator()(T* ptr)
{
//1. delete[] ptr
//2. close(ptr)
delete ptr;
}
};
template<class T, class D = Delete<T>>
class shared_ptr
{
void Release()
{
if (--(*_pCount) == 0)
{
D()(_ptr); // 定制删除器
delete _pCount;
}
}
~shared_ptr() { Release(); }
。。。
};
循环引用问题
虽然在赋值运算符重载里面用_ptr != sp._ptr
进行判断,避免管理同资源的对象间赋值,导致的重复计数问题。但是还是存在特殊的场景,诱发引用计数的错误----循环引用
struct ListNode
{
shared_ptr<ListNode> _next;
share_ptr<ListNode> _prev;
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
//同类型(share_ptr)才能进行赋值拷贝
node1->_next = node2;
node2->_prev = node1;
return 0;
}
只有当引用计数为0时资源才会释放。第一个节点引用计数变为0取决于第二个节点的prev指针,因此需要释放第二个节点,即:第二个节点的引用计数变0,这一步又取决于第一个节点的next指针…
因此第一个节点是释放取决于第二个节点,而第二个节点又需要第一个节点先释放。
weak_ptr
C++11引入的智能指针,可以与shared_ptr协同工作
在不增加对象引用计数的情况下观测和访问共享对象,不进行资源管理
- 支持使用shared_ptr对象来进行构造/赋值
// 辅助型智能指针,使命配合解决shared_ptr循环引用问题
template<class T>
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr)
{}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
//用share_ptr构建对象
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.get())
{}
weak_ptr(const weak_ptr<T>& wp)
:_ptr(wp._ptr)
{}
//用share_ptr对象进行赋值拷贝
weak_ptr<T>& operator=(const shared_ptr<T>& sp)
{
_ptr = sp.get();
return *this;
}
public:
T* _ptr;
};
对于shared_ptr问题进行修改
struct ListNode
{
weak_ptr<ListNode> _next;
weak_ptr<ListNode> _prev;
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
//weak_ptr支持shared_ptr类型的赋值拷贝
node1->_next = node2;
node2->_prev = node1;
return 0;
}
希望暂时访问一个shared_ptr所指向的对象而不想把它引用计数加1时,可以使用weak_ptr代替 shared_ptr。
🦀🦀观看~~