C++ における RAII
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 16/104。
RAII (Resource Acquisition Is Initialization) は、リソース管理をオブジェクトの生存期間(ライフタイム)に結び付ける、C++の基本的なイディオムです。考え方は単純です。コンストラクタでリソースを確保し、デストラクタでそれらを解放します。
オブジェクトが作成される際、それは必要とするあらゆるリソースを取得します。オブジェクトがスコープから外れると、そのデストラクタが自動的にそれらのリソースを解放します。これにより、例外が発生した場合でもクリーンアップが保証されます。
class FileHandler {
std::FILE* file;
public:
FileHandler(const char* filename) {
file = std::fopen(filename, "r"); // リソースを確保
}
~FileHandler() {
if (file) std::fclose(file); // リソースを解放
}
};RAIIを使用すると、クリーンアップを忘れることはありません:
void processFile() {
FileHandler handler("data.txt"); // リソースが取得される
// ファイルを操作する...
// ここで例外が発生しても、デストラクタは実行されます!
} // リソースは自動的に解放されますスマートポインタは、RAIIが実際に動作している完璧な例です。unique_ptrは、コンストラクタでヒープメモリの所有権を取得し、デストラクタでそれを削除します。あなたはすでに、気づかないうちにRAIIを使用しています!
RAIIは、メモリ、ファイルハンドル、ネットワーク接続、ミューテックス、データベース接続など、あらゆるリソースに適用されます。リソースをオブジェクトでラップすることにより、C++は決定論的なクリーンアップを保証し、リークやダングリングハンドルのようなあらゆるカテゴリのバグを排除します。
チャレンジ
簡単ネットワーク接続のようなリソースのライフサイクルを自動的に処理することで、RAIIを実証する接続マネージャーを構築しましょう。接続が作成されたときに「オープン」し、スコープ外に出たときに自動的に「クローズ」するようにします。手動でのクリーンアップは不要です!
コードを整理するために2つのファイルを作成します:
Connection.h: RAIIの原則を使用して接続リソースを管理するConnectionクラスを定義します。クラスには以下を含める必要があります:- 接続を識別するためのプライベートな
connectionId属性 (string) - 接続状態を追跡するためのプライベートな
isOpen属性 (bool) - 接続IDを受け取り、
isOpenを true に設定し、"Opening connection: <id>"と出力するコンストラクタ - 接続が開いているかを確認し、
isOpenを false に設定して閉じ、"Closing connection: <id>"と出力するデストラクタ - 接続が開いている場合に
"[<id>] Sending: <message>"と出力するsend(string message)メソッド - 接続IDを返す
getId()メソッド
- 接続を識別するためのプライベートな
main.cpp: 入力から接続IDとメッセージを読み取り、RAIIが自動クリーンアップをどのように保証するかを実証します。その後:- 波括弧
{ }を使用してスコープを作成します - そのスコープ内で、入力されたIDを使用してスタック上に
Connectionオブジェクトを作成します - 接続を使用して入力メッセージを送信します
- スコープを終了させます(接続は自動的に閉じられるはずです)
- スコープの後に、
"Connection out of scope"と出力します
- 波括弧
RAIIの素晴らしさは、接続を閉じることを覚えておく必要がないことです。オブジェクトがスコープを抜けるときに、デストラクタが自動的に処理します。このパターンは、コード内で予期しないことが起こったとしても、リソースリークを防ぎます。
main.cpp で #include "Connection.h" を使用してヘッダーファイルをインクルードしてください。
チートシート
RAII (Resource Acquisition Is Initialization) は、リソース管理をオブジェクトの生存期間に結びつけるC++のイディオムです。リソースはコンストラクタで取得され、デストラクタで解放されます。
オブジェクトがスコープ外になると、そのデストラクタが自動的にリソースを解放し、例外が発生した場合でもクリーンアップを保証します:
class FileHandler {
std::FILE* file;
public:
FileHandler(const char* filename) {
file = std::fopen(filename, "r"); // リソースを取得
}
~FileHandler() {
if (file) std::fclose(file); // リソースを解放
}
};RAIIは自動的なクリーンアップを保証します:
void processFile() {
FileHandler handler("data.txt"); // リソースが取得される
// ファイルを操作する...
// 例外がスローされても、デストラクタは実行されます!
} // リソースは自動的に解放されますunique_ptr のようなスマートポインタはRAIIの例です。これらはコンストラクタでヒープメモリを確保し、デストラクタでそれを削除します。
RAIIは、メモリ、ファイルハンドル、ネットワーク接続、ミューテックス、データベース接続など、あらゆるリソースに適用されます。これにより決定論的なクリーンアップが保証され、リークやダングリングハンドルのようなバグが排除されます。
自分で試してみよう
#include <iostream>
#include <string>
#include "Connection.h"
using namespace std;
int main() {
string connectionId;
string message;
// 入力を読み込む
cin >> connectionId;
cin.ignore();
getline(cin, message);
// TODO: 波括弧 { } を使用してスコープを作成する
// スコープ内:
// - connectionId を使用してスタック上に Connection オブジェクトを作成する
// - 接続を使用してメッセージを送信する
// スコープを終了させる(デストラクタが自動的に呼び出される)
// TODO: スコープが終了した後、"Connection out of scope" と出力する
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタ引数付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ詳解Rule of Three / Five / Zeroまとめ - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () 演算子のオーバーロード型変換演算子まとめ:Matrix クラス