C++レビュー|RAII原則によるリソース自動解放設計の徹底レビューガイド
この記事のポイント
- 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;
};
@Reviewernew/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管理が手動になっていないか
- 例外安全が構造保証されているか
を読み取り、スコープ構造に責任を埋め込む設計を支援します。