在一个函数中(或者一个{...}
作用域)有时候会创建/引用了一个资源,而在这个函数结束的时候需要对这个资源进行释放。常见的场景:
{...}
作用域开始需要加锁,执行完代码后需要解锁以上面的锁为例, 在进入函数的时候加锁,在函数退出的时候解锁。 这种写法笔者认为可能会带来两个问题:
return
, 在每个return处都加上mutex.unlock()代码感觉显得很不优雅。void function()
{
mutex.lock();
//互斥区执行代码;
//...
if(...)
{
//...
mutex.unlock();
return;
}
//...
if(...)
{
//...
mutex.unlock();
return;
}
//...
mutex.unlock();
}
而RAII
正是可以用来解决以上两个问题的,接下来我们来说说RAII
。
笔者发现很多高大上的名字背后,都是些朴实无华的东西。RAII
全称为resource acquisition is initialization
, 可能现在还无法理解这个含义,等整篇文章读完后再理解下。
RAII主要利用了如下的机制:
lock_guard
是C++11支持的,不过在此之前boost
很早实现,并被广泛使用。然后我们再以第一节的例子,使用lock_guard
来实现:
void function()
{
std::lock_guard lockGuard(mutex);
//互斥区执行代码;
//...
if(...)
{
//...
return;
}
//...
if(...)
{
//...
return;
}
//...
}
可以看到这个例子,我们做了两件事情,让代码简洁了很多:
lockGuard
的构造函数参数传递进去function
函数中所有的mutex.unlock();
那么小伙伴们结合之前的概念可能已经想到了lock_guard
的实现。以下是MSVC对lock_guard
的实现。那么可以使得:
lockGuard
的构造函数的时候,lockGuard
的成员_Mtx
对mutex
进行了引用;并且将调用了_MyMutex.lock();
。一句话描述:对象构造函数调用完毕后,则加锁了。此时是不是有点理解resource acquisition is initialization
。lockGuard
的生命周期就在函数调用结束后return
或者函数内抛出异常,则locGuard
的析构函数会调用_MyMutex.unlock();
从而实现了锁的释放。 // CLASS TEMPLATE lock_guard
template<class _Mutex>
class lock_guard
{ // class with destructor that unlocks a mutex
public:
using mutex_type = _Mutex;
explicit lock_guard(_Mutex& _Mtx)
: _MyMutex(_Mtx)
{ // construct and lock
_MyMutex.lock();
}
lock_guard(_Mutex& _Mtx, adopt_lock_t)
: _MyMutex(_Mtx)
{ // construct but don't lock
}
~lock_guard() noexcept
{ // unlock
_MyMutex.unlock();
}
lock_guard(const lock_guard&) = delete;
lock_guard& operator=(const lock_guard&) = delete;
private:
_Mutex& _MyMutex;
};
RAII是C++常用的技术,那么我们有必要去理解他,并且利用他:
{...}
来指定你的作用域(如下所示),灵活的锁定范围。void function()
{
//some code
//.......
{
RAIIObject robj(object);
//Do something
}
//some code
//......
}