C++ 惯用法之 RAII

RAII(Resource Acquisition Is Initialization)资源获取即初始化,是 C++ 中最基本、应用最广范的惯用法(idiom)之一。

RAII 的基本思想是通过构造/析构函数,对资源的获取/释放进行封装,然后借助局部对象的自动生命周期来管理资源。使用 RAII 可以让用户无需手动管理资源的获取/释放,减少出错的机会。不仅如此,RAII 还是异常安全的:即使获取资源后,在使用资源的过程中抛出异常,也可以自动释放,避免资源泄露。

C++ 标准库里面有很多 RAII 的例子,如 unique_ptr、lock_guard、fstream、string 以及 vector 等各类容器。我们在实现自己的类时,也要尽量遵循 RAII。

一些例子

C++ 核心指南 R.1: 通过资源句柄和 RAII 自动管理资源

// BAD
void send(X* x, string_view destination)
{
    auto port = open_port(destination);
    my_mutex.lock();
    // ...
    send(port, x);
    // ...
    
    // 在此之前一旦函数提前返回/抛出异常,将导致资源泄露!
    my_mutex.unlock();
    close_port(port);
    delete x;
}

// GOOD
void send(unique_ptr<X> x, string_view destination)
{
    Port port{destination};
    lock_guard<mutex> guard{my_mutex};
    // ...
    send(port, x);
    // ...
} // 自动释放 my_mutex、关闭 port,并删除 x 指向的对象

C++ 核心指南 CP.20:使用 RAII,不要直接用 lock()/unlock()

mutex mtx;

// BAD
void do_stuff()
{
    mtx.lock();
    // ...
    // 如果抛出异常,导致 mtx 不被释放!
    mtx.unlock();
}

// GOOD
void do_stuff()
{
    unique_lock<mutex> lck {mtx};
    // 即使抛出异常, mtx 会自动释放!
}

C++ 核心指南 P.8:不要泄露任何资源

// BAD
void f(char* name)
{
    FILE* input = fopen(name, "r");
    // ...
    // bad: 如果 something == true,导致文件句柄泄漏!
    if (something) return;   
    // ...
    // 如果在此之前的代码抛出异常,也会导致文件句柄泄露!
    fclose(input);
}

// GOOD
void f(char* name)
{
    ifstream input {name};
    // ...
    if (something) return;
    // ...
    // 只要离开函数作用域,自动释放文件句柄
}
posted @ 2023-07-02 17:33  Zijian/TENG  阅读(291)  评论(0编辑  收藏  举报