参考文献:
(1)刘未鹏的那个经典的帖子:C++11(及现代C++风格)快速迭代式开发
http://mindhacks.cn/2012/08/27/modern-cpp-practices/
(2)在(1)的基础上另一个人写的帖子:异常安全,RAII与C++11
http://www.cnblogs.com/mavaL/articles/2515381.html
1.RAII---资源获取即初始化
(1)What
一种管理资源,避免资源泄露的方法。
其中资源包括:堆内存、开启的线程、打开的文件等等。
当然现在标准库已经确保很多东西是RAII的了,比如
①智能指针---管理内存
②标准IO---管理IO
③线程库---管理线程,锁什么的
但是:
①像socket这样的东西还没有标准库哦。
②想标准IO这样的饱受诟病库有时候不想用,想要自己实现哦。
③比如线程库,并不是所有都已经构建的很好,像mutex这样的会有lock_guard来自动释放,似乎线程并没有。
这个时候就需要自己编写代码来实现RAII啦。
(2)Why
资源管理的两大难点嘛:
①资源泄露
编码的时候,难免出错,忘记释放已分配的资源,这个时候资源就泄露了。
②异常安全
发生异常的时候,当前程序块会终止,异常代码后面的资源释放函数来不及执行。这个算一种特殊的资源泄露吧。
(3)How
①C++标准保证了任何情况下(不管程序块正常退出,还是异常退出),已构造的对象一定会被销毁,即它的析构函数最终会被调用。
②RAII的做法就是,使用一个对象,在其初始化时获取资源,在对象析构函数里释放资源。
2.C++98对于RAII的实现难点
由于RAII要求用类的对象来管理资源。
而每种资源的释放方式又不同----归根结底,需要在对象的析构函数里调用对应资源的释放函数。
结果就是,每种资源,为了实现RAII,都要单独写一个资源的释放类。
3.C++11的解决办法
①只需要实现一个类,然后用std::function作为类对象构造函数的形参,接受不同的资源释放函数,将其放入析构函数里执行。
②实际调用时,使用Lambda作为实参,直接实现某种资源的释放操作,。
③就是RAII对象命名问题,可以采用宏定义的方式,自动获取行号为该对象命名,
然后,某次申请一个资源,就用宏构造一个释放对象,向其传入释放该资源的lambda函数即可。
这样,每次申请资源你就写一个释放对象,就不会存在忘记释放使得资源泄露的情况。
而且,遇到异常,也是能够将释放对象销毁,从而释放资源,是异常安全的。
两大问题就都解决了。
4.实际代码
以下的代码,来自刘未鹏那个帖子。
①理想情况下我们希望语言能够支持这样的范式:
void foo() { HANDLE h = CreateFile(...); ON_SCOPE_EXIT { CloseHandle(h); } ... // use the file }
<span style="font-size:18px;"><strong>②类的定义</strong></span>
class ScopeGuard
{
public:
explicit ScopeGuard(std::function<void()> onExitScope)
: onExitScope_(onExitScope), dismissed_(false) //初始化两个成员变量
{ }
~ScopeGuard()
{
if(!dismissed_)
{
onExitScope_();
}
}
void Dismiss()
{
dismissed_ = true;
}
private:
std::function<void()> onExitScope_;
bool dismissed_;
private: // noncopyable
ScopeGuard(ScopeGuard const&);
ScopeGuard& operator=(ScopeGuard const&);
};
<span style="font-size:18px;"><strong>③Dismiss()的作用</strong></span>
Dismiss()函数也是Andrei的原始设计的一部分,其作用是为了支持rollback模式,
既然可以用RAII对象的析构函数进行释放对象,也可以用来进行其他功能,比如异常发生了进行回滚,回到一个之前的已知状态。
例如:
ScopeGuard onFailureRollback([&] { /* rollback */ }); ... // do something that could fail onFailureRollback.Dismiss();
在上面的代码中,“do something”的过程中只要任何地方抛出了异常,rollback逻辑都会被执行。如果“do something”成功了,onFailureRollback.Dismiss()会被调用,设置dismissed_为true,阻止rollback逻辑的执行。
为了避免给这个对象起名的麻烦(如果有多个变量,起名就麻烦大了),可以定义一个宏,把行号混入变量名当中,
这样每次定义的ScopeGuard对象都是唯一命名的。
#define SCOPEGUARD_LINENAME_CAT(name, line) name##line #define SCOPEGUARD_LINENAME(name, line) SCOPEGUARD_LINENAME_CAT(name, line) #define ON_SCOPE_EXIT(callback) ScopeGuard SCOPEGUARD_LINENAME(EXIT, __LINE__)(callback)
其中_LINE_会因为具体行不同,生成不同数字,这样就有不同的名字了,但是每次调用时都是一致的名字。
⑤最终形式
Acquire Resource1 ON_SCOPE_EXIT( [&] { /* Release Resource1 */ }) Acquire Resource2 ON_SCOPE_EXIT( [&] { /* Release Resource2 */ }) …