RAII
C++での例 > ファイルハンドルの管理

別の例として、ファイルのオープンとクローズを挙げる。従来の標準Cライブラリを使って、直接リソースを管理する書き方だと以下のようになる。
#include <cstdio>
#include <cassert>
#include <stdexcept>

FILE* openFile(const char* fileName, const char* mode) {
    FILE* fp = std::fopen(fileName, mode);
    if (!fp) {
        throw std::runtime_error("Failed to open file!");
    }
    return fp;
}

void writeLine(FILE* fp, const char* strLine) {
    assert(fp);
    const int ret = std::fprintf(fp, "%s\n", strLine);
    if (ret < 0) {
        throw std::runtime_error("Failed to write data on file!");
    }
}

void function2A() {
    FILE* fp1 = NULL;
    FILE* fp2 = NULL;
    try {
        fp1 = openFile("test1.txt", "a");
        fp2 = openFile("test2.txt", "a");
        // ファイルの書き込みを行なう。
        writeLine(fp1, "Test line for file#1.");
        writeLine(fp2, "Test line for file#2.");
        // ファイルを使い続ける。
        // 何か問題が起こって関数を抜ける場合、return の前に fclose() を忘れずに呼ばなければならない。

        // 明示的にファイルを閉じる必要がある。
        std::fclose(fp1);
        std::fclose(fp2);
    }
    catch (...) {
        // 獲得したリソースがあれば返却する。
        if (fp1) {
            std::fclose(fp1);
        }
        if (fp2) {
            std::fclose(fp2);
        }
        throw; // 例外の再送出。
    }
}
一方、RAIIを利用した場合は以下のようになる。
class FileWrapper {
    FILE* m_fp;
public:
    FileWrapper(const char* fileName, const char* mode)
        : m_fp(std::fopen(fileName, mode)) { // ファイルハンドルでデータメンバーを初期化。
        if (!m_fp) {
            throw std::runtime_error("Failed to open file!");
        }
    }
    ~FileWrapper() {
        assert(m_fp);
        std::fclose(m_fp);
    }
    void writeLine(const char* strLine) {
        assert(m_fp);
        const int ret = std::fprintf(m_fp, "%s\n", strLine);
        if (ret < 0) {
            throw std::runtime_error("Failed to write data on file!");
        }
    }
    // コピーは禁止とする。所有権の移動もサポートしない。
private:
    FileWrapper(const FileWrapper&);
    FileWrapper& operator=(const FileWrapper&);
};

void function2B() {
    FileWrapper file1("test1.txt", "a");
    file1.writeLine("Test line for file#1.");
    FileWrapper file2("test2.txt", "a");
    file2.writeLine("Test line for file#2.");
} // 関数の途中で return したり、例外がスローされたりしても、RAII 変数の属するブロックを抜けた時点で確実にファイルハンドルは閉じられる。
標準C++ライブラリでは、抽象化されたファイルストリーム管理用のRAIIクラスとして、std::basic_fstreamが用意されている。
FileWrapperクラスではFILE*をカプセル化したが、RAIIの真髄は有限の資源ならば何でも同様に管理できることにある。そしてRAIIでは、関数（やその他ブロック）を抜けるときに適切に資源が破棄されることが保証される。なお、FileWrapperクラスのコンストラクタはファイルが開けなければ例外を投げるため、インスタンスが生成されていれば内包するファイルハンドルは常に利用可能であると仮定してよい。
RAIIを使わない場合、例外が発生するとある問題が生じる。複数の資源を確保する際、それぞれの確保の間に例外が投げられたら、catchブロックではどれを解放すべきか分からなくなってしまう（通常、確保されていない資源を解放することはできない）。function1Aやfunction2Aのように、初期値を無効と見なすことにして二段階初期化したり、try-catchブロックを重ねていったりするなど、状況に応じてコードを適切に書いていかなければ安全性は得られない。function1Bやfunction2BのようなRAIIならばこれにも対処できる。変数（メンバー変数も含む）が構築されたときとは逆順で破棄され、また完全に構築された（内部で例外が投げられずにコンストラクタが実行された）オブジェクトのみが破棄されるので問題は起こらない。これはプログラムが資源（またはそれに類するもの）の管理から逃れることができるようになったということである。RAIIクラスを定義する手間はかかるが、いくつもの関数でRAIIクラスを使っていれば、コードの再利用により全体的にはコードが単純化し、良いプログラムにする手助けとなる。また、副次効果としてビジネスロジックの記述に集中することができるようになる。
なお、function1Aやfunction2AはJavaのようなRAIIでない言語での資源管理に使われるイディオムに似ている。Javaのtry-finallyブロックは資源の確実な返却を実行するポイントを提供するが、都度try-finallyブロックで適切に破棄処理を書かなければならず、プログラマに負担がかかる。