啥是RAII

按理说,您要是了解lambda的话,没有理由不知道RAII,但是小麦还是先提一下吧,为了本文的完整性嘛。RAII,Resource Acqueision Is Initialization,也就是资源获取即初始化。也就是说,在控制资源的声明周期的时候,在某个对象初始化(构造)的时候获取资源,而在对象销毁的时候(析构的时候释放资源。这一技术被广泛的使用在mutex中,典型的代码如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
template<class LT>;
class guard_lock{
public:
    guard_lock(LT &loc)
    : m_lt(loc){m_lt.lock();}

    ~guard_lock(){m_lt.unlock();}
protected:
    LT &   m_lt;
};//end class guard_lock;

这样一来,这个就能够很给力的做到 即使释放锁资源,例如下面的代码:

1
2
3
4
5
6
7
8
guard_lock<Mutex> _l(mutex);
...
if(...)
    return ;
if(another)
    return ;

a_func_may_throw_exception();

通常,修改一下代码,然后,增加一个if条件,增加几个return是很正常的,如果不使用这样的RAII技术的话,就要在每个return的地方写上unlock()。RAII一下就解决了代码重复和维护性的问题。

问题

RAII看上去很强大,有什么问题么? 当然是有的,首先,每种资源的获取和释放方式都意味着不同的代码,这意味着对每种资源可能都需要特别的”guard_lock”,这。。。也很蛋疼,看一个例子也许能够更好的理解这种问题。实际中一个常见的问题是读一个文件,处理其中的数据,然后写文件。实际的编码中通常需要考虑读文件的路径不对的话,怎么办?写文件的路径不对呢?处理数据遇到异常了怎么办?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
ifstream infile;
infile.open(infilename, ios::binary | ios::in);
if(!infile.is_open())
{
    infile.close();
    return -1;
}
ofstream outfile;
outfile.open(outfilename, ios::binary | ios::out);
if(!outfile.is_open())
{
    outfile.close();
    infile.close();
}

...
if(...) //other error happens
{
    outfile.close();
    infile.close();
}

这个例子中,文件的关闭操作被一再的调用和执行,这对于“相同代码只写一次”的强迫症来说,简直就是不能忍受的!

利用lambda

C++11引入了lambda,因此我们可以实现更加抽象的RAII类,在构造是执行一个函数,在析构时执行另外一个函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class scope_guard{
public:
    template<class F1_t, class F2_t>
    scope_guard(F1_t &&ctor, F2_t &&dtor)
    : m_ctor(std::forward<F1_t>(ctor))
    , m_dtor(std::forward<F2_t>(dtor)){
        m_ctor();
    }

    ~scope_guard(){m_dtor();}
protected:
    std::function<void(void)> m_ctor;
    std::function<void(void)> m_dtor;
};//end class scope_guard;

有了这个辅助类,上面的苦逼代码就能进化了!!!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
ifstream infile;
scope_guard sg_infile([&infile, &infilename]()      {infile.open(infilename, ios::binary | ios::in);},
           [&infile]() {infile.close();});
ofstream outfile;
scope_guard sg_outfile([&outfile, &outfilename]() {outfile.open(outfilename, ios::binary | ios::out);},
            [&outfile]() {outfile.close();});

if(!infile.is_open()) {
    cout<<"cannot open file: "<< infilename<<endl;
    return 1;
}
if(!outfile.is_open()) {
    cout<<"cannot open file: "<<outfilename<<endl;
    return 1;
}

有没有再也不用担心忘记错误处理的感觉!!!