RAII技术

RAII是C++的发明者Bjarne Stroustrup提出的概念,RAII全称是“Resource Acquisition is Initialization”,直译过来是“资源获取即初始化”,也就是说在构造函数中申请分配资源,在析构函数中释放资源。因为C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定。

✏️ 1、资源管理

RAII是用来管理资源、避免资源泄漏的方法。在计算机系统中,资源是数量有限且对系统正常运行具有一定作用的元素。比如:网络套接字、互斥锁、文件句柄和内存等等,它们属于系统资源。由于系统的资源是有限的,所以,我们在编程使用系统资源时,都必须遵循一个步骤:

  1. 申请资源;

  2. 使用资源;

  3. 释放资源。

第一步和第二步缺一不可,因为资源必须要申请才能使用的,使用完成以后,必须要释放,如果不释放的话,就会造成资源泄漏。

智能指针(std::shared_ptrstd::unique_ptr)即RAII最具代表的实现,使用智能指针,可以实现自动的内存管理,再也不需要担心忘记delete造成的内存泄漏。(博客

#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)

class ScopeGuard
{
public:
    explicit ScopeGuard(std::function<void()> f) : 
        handle_exit_scope_(f){};

    ~ScopeGuard(){ handle_exit_scope_(); }
private:
    std::function<void()> handle_exit_scope_;
};

int main()
{
    {
        A *a = new A();
        ON_SCOPE_EXIT([&] {delete a; });
        ......
    }

    {
        std::ofstream f("test.txt");
        ON_SCOPE_EXIT([&] {f.close(); });
        ......
    }

    system("pause");
    return 0;
}

使用C++11标准中的lambda表达式和std::function相结合的方法,定义了根据行号来对ScopeGuard类型对象命名的宏定义。当ScopeGuard对象超出作用域,ScopeGuard的析构函数中会调用handle_exit_scope函数,也就是lambda表达式中的内容,所以在lambda表达式中填上资源释放的代码即可。既不需要为每种资源管理单独写对应的管理类,也不需要考虑手动释放出现各种异常情况下的处理,同时资源的申请和释放放在一起去写。

✏️ 2、状态管理

RAII另一个引申的应用是可以实现安全的状态管理。一个典型的应用就是在线程同步中,使用std::unique_lock或者std::lock_guard对互斥量std:: mutex进行状态管理。通常我们不会写如下的代码:

std::mutex mutex_;
void function()
{
    mutex_.lock();
    ......
    ......
    mutex_.unlock();
}

因为,在互斥量lock和unlock之间的代码很可能会出现异常,或者有return语句,这样的话,互斥量就不会正确的unlock,会导致线程的死锁。所以正确的方式是使用std::unique_lock或者std::lock_guard对互斥量进行状态管理:

std::mutex mutex_;
void function()
{
    std::lock_guard<std::mutex> lock(mutex_);
    ......
    ......
}

在创建std::lock_guard对象的时候,会对std::mutex对象进行lock,当std::lock_guard对象在超出作用域时,会自动std::mutex对象进行解锁,这样的话,就不用担心代码异常造成的线程死锁。

最后更新于