RAII

RAII，全稱資源獲取即初始化（Resource Acquisition Is Initialization），它是在一些面向對象語言中的一種慣用法。RAII源於C++，在Java，C#，D，Ada，Vala和Rust中也有應用。1984-1989年期間，比雅尼·斯特勞斯特魯普和安德魯·柯尼希在設計C++異常時，為解決資源管理時的異常安全性而使用了該用法，後來比雅尼·斯特勞斯特魯普將其稱為RAII。

RAII要求，資源的有效期與持有資源的對象生命周期嚴格綁定，即由對象的構造函數完成資源的分配（獲取），同時由析構函數完成資源的釋放。在這種要求下，只要對象能正確地析構，就不會出現資源洩漏問題。

## 作用

RAII的主要作用是在不失代碼簡潔性的同時，可以很好地保證代碼的異常安全性。

下面的C++實例說明了如何用RAII訪問文件和互斥量：

```cpp
#include <string>
#include <mutex>
#include <iostream>
#include <fstream>
#include <stdexcept>
 
void write_to_file(const std::string & message)
{
    // 建立文件的互斥鎖
    static std::mutex mutex;
 
    // 訪問文件前加鎖
    std::lock_guard<std::mutex> lock(mutex);
 
    // 試著打開文件
    std::ofstream file("example.txt");
    if (!file.is_open())
        throw std::runtime_error("unable to open file");
 
    // 輸出內容
    file << message << std::endl;
 
    // 當離開時，文件會被解構（不管是否有異常）
    // 互斥鎖也會被解構（不管是否有異常）
}
```

C++保證了所有棧對象在生命周期結束時會被銷毀（即調用析構函數），所以該代碼是異常安全的。無論在write_to_file函數正常返回時，還是在途中拋出異常時，都會引發write_to_file函數的堆棧回退，而此時會自動調用lock和file對象的析構函數。

當一個函數需要通過多個局部變量來管理資源時，RAII就顯得非常好用。因為只有被構造成功（構造函數沒有拋出異常）的對象才會在返回時調用析構函數，同時析構函數的調用順序恰好是它們構造順序的反序，這樣既可以保證多個資源(對象)的正確釋放，又能滿足多個資源之間的依賴關係。

由於RAII可以極大地簡化資源管理，並有效地保證程序的正確和代碼的簡潔，所以通常會強烈建議在C++中使用它。

## 典型用法

RAII在C++中的應用非常廣泛，如C++標準庫中的lock_guard便是用RAII方式來控制互斥量:

```cpp
template <class Mutex> class lock_guard {
private:
    Mutex& mutex_;

public:
    lock_guard(Mutex& mutex) : mutex_(mutex) { mutex_.lock(); }
    ~lock_guard() { mutex_.unlock(); }

    lock_guard(lock_guard const&) = delete;
    lock_guard& operator=(lock_guard const&) = delete;
};
```

程式設計師可以非常方便地使用lock_guard，而不用擔心異常安全問題

```cpp
extern void unsafe_code();  // 可能抛出异常

using std::mutex;
using std::lock_guard;

mutex g_mutex;

void access_critical_section()
{
    lock_guard<mutex> lock(g_mutex);
    unsafe_code();
}
```

實際上，C++標準庫的實現就廣泛應用了RAII，典型的如容器、智能指針等。

## RRID

RAII還有另外一種被稱為RRID（Resource Release Is Destruction）的特殊用法，即在構造時沒有「獲取」資源，但在析構時釋放資源。ScopeGuard和Boost.ScopeExit就是RRID的典型應用：

```cpp
#include <functional>

class ScopeGuard {
private:
    typedef std::function<void()> destructor_type;

    destructor_type destructor_;
    bool dismissed_;

public:
    ScopeGuard(destructor_type destructor) : destructor_(destructor), dismissed_(false) {}

    ~ScopeGuard()
    {
        if (!dismissed_) {
            destructor_();
        }
    }

    void dismiss() { dismissed_ = true; }

    ScopeGuard(ScopeGuard const&) = delete;
    ScopeGuard& operator=(ScopeGuard const&) = delete;
};
```

ScopeGuard通常用於省去一些不必要的RAII封裝，例如

```cpp
void foo()
{
    auto fp = fopen("/path/to/file", "w");
    ScopeGuard fp_guard([&fp]() { fclose(fp); });

    write_to_file(fp);                     // 异常安全
}
```

在D語言中，scope關鍵字也是典型的RRID用法，例如

```d
void access_critical_section()
{
    Mutex m = new Mutex;
    lock(m); 
    scope(exit) unlock(m);

    unsafe_code();                  // 异常安全
}

Resource create()
{
    Resource r = new Resource();
    scope(failure) close(f);

    preprocess(r);                  // 抛出异常时会自动调用close(r)
    return r;
}
```

## 與finally的比較

雖然RAII和finally都能保證資源管理時的異常安全，但相對來說，使用RAII的代碼相對更加簡潔。

如比雅尼·史特勞斯特魯普所說，「在真實環境中，調用資源釋放代碼的次數遠多於資源類型的個數，所以相對於使用finally來說，使用RAII能減少代碼量。」

例如在Java中使用finally來管理Socket資源

```java
void foo() {
    Socket socket;
    try {
        socket = new Socket();
        access(socket);
    } finally {
        socket.close();
    }
}
```

在採用RAII後，代碼可以簡化為

```java
void foo() {
    try (Socket socket = new Socket()) {
        access(socket);
    }
}
```

特別是當大量使用Socket時，重複的finally就顯得沒有必要。