RAII技术,很有意思,与其说是一个技术,不如说是一个编程上的窍门。我们平常编程时也可能会用到,在stl模板库里也有体现,但可能我们并不知道它的名字。
RAII,即“Resource acquisition is initialization”,也就是“资源获取就是初始化”。恩,就是这样。啥意思?不知道(个人感觉这个名字确实取得不怎么样)。
好,让我们抛开名字,直接用它的简写RAII。RAII简单的说,是为了防止诸如内存泄露、资源泄露等情况产生的一种编程技巧。我们平常在编程时,经常会遇到申请堆内存、获取windows系统资源handle的情况。而这些情况下,均需要我们手动的显示释放你之前申请的内存或资源。我们可以理解为有“借”必须有“还”(在运用锁的情况下,可以看做有“关”必须有“开”,不然就会死锁)。
但是,你能够保证做到你编写的代码有“借”了,一定就会“还”吗?比如我们获取了windows文件资源的句柄,接下来我们执行了若干操作,中间可能有某些条件下的return语句,甚至还会抛出异常,你能足够细心在每个return及异常的catch中释放句柄资源吗?好,即使我们都释放了,那么代码中会有多处重复的释放资源代码,总是不够那么优雅。
不用慌张,RAII能让我们优雅的做完释放资源这件事。C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终一定会被调用。我们前面说过,“借”了,一定要“还”。注意到那两个红色的“一定”了吗?一个是C++中一定会调用的函数,一个是我们一定要做的事。那我们把一定要做的事情放到一定会调用的函数中可以吗?这样我们就保证了一定要做的事最终一定会发生!
没错,RAII就是利用析构函数一定会被调用的特性(不管是在return还是在异常退出的情况下),将释放资源的代码写到类的析构函数中。在类对象初始化的时候,将资源传入该类对象中“托管”,并利用类对象最终调用析构函数将托管的资源释放掉。
这样,资源的生命周期就等同于类对象的生命周期。
我们提炼出RAII的三个关键词:
- 构造函数 (将资源传入类对象,实现“托管”)
- 生命周期(在类对象的生命周期内,托管的资源不会释放)
- 析构函数(当类对象生命周期结束(或异常退出时,在进入catch语句前,会自动调用对象析构函数),调用其析构函数,托管的资源在析构函数中同时也释放掉)
// HandleMgr.h
class CHandleMgr
{
public:
CHandleMgr(const HANDLE& khandle);
virtual ~CHandleMgr();
void ReleaseHandle();
HANDLE& GetHandle() { return m_handle; }
private:
HANDLE m_handle;
bool m_bReleased;
};
// HandleMgr.cpp
CHandleMgr::CHandleMgr(const HANDLE& khandle) : m_handle(khandle), m_bReleased(false)
{}
CHandleMgr::ReleaseHandle()
{
if (m_handle != nullptr)
{
CloseHandle(m_handle);
m_handle = nullptr;
}
m_bReleased = true;
}
CHandleMgr::~CHandleMgr
{
try
{ if (!m_bReleased) { if (m_handle != nullptr) { CloseHandle(m_handle); m_handle = nullptr; } }
}
catch(...) { // do sty }
}
注意,1、在CHandleMgr类中,我添加了一个ReleaseHandle方法,这样,我们通过显示调用 ReleaseHandle来释放资源,不必非要等到CHandleMgr类生命周期结束。
CTestClass在main函数的代码中,有可能会抛出异常1。好,当异常1抛出后,被main中的try捕获到,在进入catch前,调用CTestClass的析构函数,但不幸的是,这时候析构函数也抛出了异常2,由于这里只有一个try,并且被异常1用掉了,那么异常2则不会被捕获,导致程序的崩溃。
{
public:
….
~CTestClass()
{
….
//may throw exception 2
}
…..
};
int main()
{
try
{
……….
CTestClass a;
………
// may throw exception 1
}catch(…)
{
// do sht
}
}
template <class _Mutex>而在std::ofstream中,则实现了对文件handle的自动管理,类似我上面的代码,有兴趣的话大家可以看一下stl源码是怎样实现的。
class _LIBCPP_TYPE_VIS lock_guard
{
public:
typedef _Mutex mutex_type;
private:
mutex_type& __m_;
public:
_LIBCPP_INLINE_VISIBILITY
explicit lock_guard(mutex_type& __m) // 构造函数,接收锁资源,并自动上锁
: __m_(__m) {__m_.lock();}
_LIBCPP_INLINE_VISIBILITY
lock_guard(mutex_type& __m, adopt_lock_t)
: __m_(__m) {}
_LIBCPP_INLINE_VISIBILITY
~lock_guard() {__m_.unlock();} // 析构函数, 释放锁资源
private:
lock_guard(lock_guard const&);// = delete;
lock_guard& operator=(lock_guard const&);// = delete;
};