この記事のポイント

  • RAII原則の設計責務をレビューアー視点で読み解ける
  • リソース解放漏れの構造的原因をレビュー段階で摘み取れる
  • 例外安全性まで統合したリソース管理をレビューできる

そもそもRAIIとは

RAII(Resource Acquisition Is Initialization)は、C++設計における基本原則の一つです。

定義
  • リソースの獲得は初期化で行い
  • スコープ終了時に自動で解放される構造に設計する

C++のデストラクタはスコープを抜けた瞬間に必ず呼ばれるため、
コンストラクタで取得 → デストラクタで解放
という構造に寄せることで以下が保証されます。

  • 例外安全性
  • 破棄漏れ防止
  • リソース責任の明確化

レビューアーは「リソース責任がスコープに紐付いているか」を読み取ります。

なぜこれをレビューするのか

RAIIが崩れている設計は以下を引き起こします。

  • 明示的なdelete/close忘れ
  • 例外時のリソースリーク
  • 複雑なfinally相当処理の乱立
  • 保守負荷の急上昇

RAIIは例外安全設計レビューの土台でもあります。

レビューアー視点

  • リソース獲得をコンストラクタ集中に寄せているか
  • スコープ終了と同時に解放可能な設計か
  • 例外時の途中リソース解放が構造的に保証されているか
  • 手動解放API設計になっていないか確認する

開発者視点

  • new/deleteを使わない設計に寄せる
  • スマートポインタを活用して所有責任を集約
  • ファイル/DB/通信/ロックなど全リソースでRAII徹底
  • try-finally的コードは設計敗北として減らす

良い実装例

想定:APIリクエストログをファイル出力

良い設計例
#include <fstream>
#include <string>

class ApiRequestLogWriter {
public:
    ApiRequestLogWriter(const std::string& filename)
        : file_(filename, std::ios::out)
    {
        if (!file_) {
            throw std::runtime_error("Failed to open log file");
        }
    }

    void write(const std::string& message) {
        file_ << message << std::endl;
    }

private:
    std::ofstream file_;  // RAII:スコープ終了時に自動クローズ
};

void processRequest() {
    ApiRequestLogWriter writer("request.log");
    writer.write("Request received");
}

良いポイント

  • コンストラクタでリソース取得
  • スコープ終了時に自動解放
  • 例外が発生してもリークしない

レビュー観点

  • コンストラクタ集中でリソース獲得しているか
  • デストラクタでリソース確実解放される構造か
  • 例外安全性が自然に保証されているか
  • finally相当の手動処理が不要になっているか
  • 外部APIでもRAII構造にラッピングされているか

良くない実装例: ケース1(手動クローズ依存設計)

問題例①
class ApiRequestLogWriter {
public:
    void open(const std::string& filename) {
        file_ = new std::ofstream(filename, std::ios::out);
        if (!(*file_)) {
            throw std::runtime_error("Failed to open");
        }
    }

    void write(const std::string& message) {
        (*file_) << message << std::endl;
    }

    void close() {
        if (file_) {
            file_->close();
            delete file_;
            file_ = nullptr;
        }
    }

private:
    std::ofstream* file_ = nullptr;
};
@Reviewer
new/deleteを使用せず、ofstreamをメンバ変数として直接保持してください。open/closeの手動制御を排除し、コンストラクタ集中のRAII設計に修正しましょう。

問題点

  • リソース解放忘れの余地が発生
  • 例外発生時に解放処理が漏れやすい
  • finally的コードが増殖する温床

改善例

改善例①
class ApiRequestLogWriter {
public:
    ApiRequestLogWriter(const std::string& filename)
        : file_(filename, std::ios::out)
    {
        if (!file_) {
            throw std::runtime_error("Failed to open log file");
        }
    }

    void write(const std::string& message) {
        file_ << message << std::endl;
    }

private:
    std::ofstream file_;
};

良くない実装例: ケース2(例外安全性の崩壊)

問題例②
void processRequest() {
    std::ofstream* file = new std::ofstream("request.log");
    (*file) << "Request received" << std::endl;

    // 途中で例外発生
    throw std::runtime_error("DB error");

    file->close();
    delete file;
}
@Reviewer
ローカル変数でofstreamオブジェクトを直接保持してください。RAII設計に移行することで例外安全性が自然に保証されます。

問題点

  • 例外が出るとclose/deleteされない
  • finally処理を強制される設計になる
  • 短命コードであっても設計原則が崩れる

改善例

改善例②
void processRequest() {
    std::ofstream file("request.log");
    file << "Request received" << std::endl;

    throw std::runtime_error("DB error");  // ここでも自動解放
}

良くない実装例: ケース3(外部リソースのRAIIラッピング不足)

問題例③
class ExternalResource {
public:
    void acquire() { /* 外部API呼び出し */ }
    void release() { /* 外部API解放 */ }
};

void useResource() {
    ExternalResource resource;
    resource.acquire();

    // 途中で例外発生
    throw std::runtime_error("Processing error");

    resource.release();
}
@Reviewer
外部リソース解放もRAIIラッパークラスを定義してください。acquire/releaseをコンストラクタ・デストラクタに集約し、例外安全性を確保しましょう。

改善例

改善例③
class ExternalResourceGuard {
public:
    ExternalResourceGuard(ExternalResource& resource)
        : resource_(resource)
    {
        resource_.acquire();
    }

    ~ExternalResourceGuard() {
        resource_.release();
    }

private:
    ExternalResource& resource_;
};

void useResource() {
    ExternalResource res;
    ExternalResourceGuard guard(res);

    throw std::runtime_error("Processing error");
}

観点チェックリスト


まとめ

RAIIレビューは「破棄責任の自動化レビュー」とも言えます。
レビューアーは常に

  • new/deleteが露出していないか
  • open/close管理が手動になっていないか
  • 例外安全が構造保証されているか

を読み取り、スコープ構造に責任を埋め込む設計を支援します。

UML Diagram