RAII/SBRM简单接触

RAII基本含义

Resource Acquisition Is Initialization(RAII)是一种设计思想,为了实现:

  1. 保证资源在作用域结束后被释放
  2. 提供基本的异常安全

RAII这个名字不够直观,实际上另一个名字更能体现这种思想,Scope-Bound Resource Management(SBRM),即作用域界定资源管理。

设计动机

资源管理难题

资源在离开函数作用域前,应当被释放,除非它的管理权被转移到另一个作用域。因此,资源获取和资源释放的函数经常成对出现。然而,尽管我们写了资源释放函数,但他们可能由于return或者exception导致的程序控制流离开作用域而不会被调用。例如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void foo ()
{
char * ch = new char [100];
if (...)
if (...)
return;
else if (...)
if (...)
else
throw "ERROR";

delete [] ch; // This may not be invoked... memory leak!
}
void bar ()
{
lock.acquire();
if (...)
if (...)
return;
else
throw "ERROR";

lock.release(); // This may not be invoked... deadlock!
}

这是一个普遍存在的控制流抽离问题。RAII思想可以帮助我们更方便的实现资源的管理。

解决方案

可以利用C++语言对于析构对象的特性,将资源释放的操作放入到一个对象实例中。C++保证析构函数总会在控制流离开作用域时被调用,不论是否发生return或者exception。代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//  Private copy constructor and copy assignment ensure classes derived from class NonCopyable cannot be copied.
class NonCopyable
{
NonCopyable (NonCopyable const &); // private copy constructor
NonCopyable & operator = (NonCopyable const &); // private assignment operator
};

// 一般的资源
template <class T>
class AutoDelete : NonCopyable
{
public:
AutoDelete (T * p = 0) : ptr_(p) {}
~AutoDelete () throw() { delete ptr_; }
private:
T *ptr_;
};

// 锁
class ScopedLock : NonCopyable// Scoped Lock idiom
{
public:
ScopedLock (Lock & l) : lock_(l) { lock_.acquire(); }
~ScopedLock () throw () { lock_.release(); }
private:
Lock& lock_;
};

void foo ()
{
X * p = new X;
AutoDelete<X> safe_del(p); // Memory will not leak
p = 0;
// Although, above assignment "p = 0" is not necessary
// as we would not have a dangling pointer in this example.
// It is a good programming practice.

if (...)
if (...)
return;

// No need to call delete here.
// Destructor of safe_del will delete memory
}
void X::bar()
{
ScopedLock safe_lock(l); // Lock will be released certainly
if (...)
if (...)
throw "ERROR";
// No need to call release here.
// Destructor of safe_lock will release the lock
}

python的with方法,类似于这种设计思想。

Lock_guard对mutex的管理

c++11中lock_guard对mutex互斥锁的管理就是典型的RAII机制。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/// @brief  Scoped lock idiom.
// Acquire the mutex here with a constructor call, then release with
// the destructor call in accordance with RAII style.
template<typename _Mutex>
class lock_guard
{
public:
typedef _Mutex mutex_type;

explicit lock_guard(mutex_type& __m) : _M_device(__m)
{ _M_device.lock(); }//构造对象时加锁(申请资源),构造函数结束,就可以正常使用资源了

lock_guard(mutex_type& __m, adopt_lock_t) : _M_device(__m)
{ } // calling thread owns mutex

~lock_guard()
{ _M_device.unlock(); }//析构对象时解锁(释放资源)
// 禁用拷贝构造函数
lock_guard(const lock_guard&) = delete;
// 禁用赋值操作符
lock_guard& operator=(const lock_guard&) = delete;

private:
mutex_type& _M_device;
};

为了保证lock_guard对象不被错误使用而产生不可预知的后果,因此lock_guard对象删除了拷贝构造和赋值运算符,保证lock_guard不被复制。

参考文献

https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Resource_Acquisition_Is_Initialization

https://blog.csdn.net/10km/article/details/49847271