【C++】RAIIとは?|リソース管理の基本思想をゲーム開発で理解する

C++のRAIIのイメージ|リソース管理の基本思想 C++

今回は、C++でよく出てくる「RAIIとは何か」について解説していきます。

「RAIIって呪文みたいで意味が分からない」
「デストラクタで後始末するのは知ってるけど、それがRAII?」
「スマートポインタと何が関係あるの?」

こんな疑問はありませんか?

RAIIは名前こそ難しそうですが、中身は「使い始めるときに用意して、スコープを抜けたら自動で片づける」という、たったこれだけの考え方です。C++の安全なコードは、ほとんどがこの思想の上に成り立っています。

この記事を読み終えると、あなたはRAIIの意味・なぜ必要なのか・書き方・ゲーム開発での使いどころ・初心者がハマりやすいミスをしっかり理解できると思いますので、ぜひ最後まで読んでいただけると嬉しいです。

RAIIとは?|名前の意味

RAIIは Resource Acquisition Is Initialization の略で、日本語にすると「リソースの確保=初期化」です。少し意訳すると、「リソースの寿命を、オブジェクトの寿命に結びつける」という意味になります。

  • コンストラクタでリソースを確保する
  • デストラクタでリソースを解放する
  • スコープを抜けるとデストラクタが自動で呼ばれるので、解放し忘れが起きない

ここでいう「リソース」は、メモリだけではありません。ファイル・ロック・テクスチャ・サウンド・ネットワーク接続など、「使ったら必ず後始末が必要なもの」すべてが対象です。仕組みの土台はデストラクタなので、コンストラクタとデストラクタの記事も合わせて読むと理解が深まります。

なぜRAIIが必要なのか

手動で後始末をするコードは、解放を書き忘れたり、途中でreturnしてすり抜けたりして、簡単にリソース漏れを起こします。

void LoadStage() {
    FILE* fp = fopen("stage.dat", "rb");
    if (!ReadHeader(fp)) {
        return; // ★ fclose を忘れてリソース漏れ!
    }
    // ...読み込み処理...
    fclose(fp);
}

途中の returnfclose を飛ばしてしまいました。こういう「うっかり」を、仕組みで根絶するのがRAIIです。

RAIIを自分で書いてみる

ファイルを「確保したら必ず閉じる」クラスにしてみましょう。

class FileHandle {
public:
    // 確保 = 初期化
    explicit FileHandle(const char* path) {
        fp_ = fopen(path, "rb");
    }
    // 解放 = デストラクタ
    ~FileHandle() {
        if (fp_) fclose(fp_);
    }
    FILE* Get() const { return fp_; }

private:
    FILE* fp_ = nullptr;
};

void LoadStage() {
    FileHandle file("stage.dat");
    if (!ReadHeader(file.Get())) {
        return; // ★ ここで抜けても自動で fclose される
    }
    // ...読み込み処理...
} // ★ 関数を抜けた瞬間、~FileHandle() が呼ばれる

どこで return しても、例外が飛んでも、スコープを抜ければ必ずデストラクタが呼ばれて閉じられます。もう「閉じ忘れ」を心配する必要がありません。

身近なRAIIの例|実は毎日使っている

難しく考えなくても、あなたはすでにRAIIを使っています。代表格がスマートポインタです。

  • std::unique_ptr … スコープを抜けると自動で delete
  • std::vector … 寿命が尽きると確保したメモリを自動で解放
  • std::lock_guard … スコープを抜けると自動でロック解除

これらは全部「RAIIで作られた道具」です。つまりRAIIを理解することは、標準ライブラリがなぜ安全なのかを理解することでもあります。

【重要】私がRAIIのありがたみを痛感した体験談

自主制作のゲームで、テクスチャの読み込みと解放を手動で管理していた頃、シーンを切り替えるたびにメモリがじわじわ増え続けるという不具合に悩まされました。

原因は、あるエラー処理の分岐でテクスチャの解放を1か所だけ書き忘れていたこと。分岐が増えるほど、こういう漏れは必ず出ます。そこでテクスチャを「確保したら必ず解放する」RAIIクラスに包んだところ、解放処理をコードから全部消せて、リークがピタッと止まりました

「後始末を人間が覚えておく」のをやめて、オブジェクトの寿命に任せる。これがどれだけ楽で安全か、身をもって実感した出来事でした。

RAIIのよくある失敗例と対処法

①コピーして二重解放

RAIIクラスを何気なくコピーすると、同じリソースを2つのオブジェクトが持ち、両方がデストラクタで解放してクラッシュします。コピーを禁止するか、ムーブだけ許可するのが基本です。

②デストラクタで例外を投げる

デストラクタから例外を投げると、プログラムが即座に終了することがあります。後始末の中でエラーが出ても、デストラクタの外に例外を漏らさないようにします。

③new したポインタを生のまま持つ

せっかくRAIIを学んでも、生ポインタで new していては意味がありません。まずは unique_ptr に置き換えるだけで、多くのリークが消えます。

注意点

  • RAIIクラスはコピーの扱いを必ず決める(禁止 or ムーブのみ)
  • デストラクタから例外を外に漏らさない
  • 自作する前に、まず標準の unique_ptr / vector で済まないか考える

まとめ

  • RAIIはリソースの寿命をオブジェクトの寿命に結びつける考え方
  • 確保はコンストラクタ、解放はデストラクタで自動化する
  • スマートポインタやvectorもRAIIで作られた道具
  • ハマりどころはコピーによる二重解放

RAIIは、C++で安全なコードを書くための土台となる思想です。「後始末は書かない。オブジェクトに任せる」——この感覚が身につくと、コードが一気に堅牢になります。まずは生ポインタを unique_ptr に置き換えるところから始めてみてください。

関連記事