[解決済み] 用語の意味と概念の理解 - RAII (Resource Acquisition is Initialization)
質問
C++の開発者の方々は、RAIIとは何か、なぜそれが重要なのか、そして他の言語との関連はあるのかどうか、きちんと説明していただけませんか?
I する は少し知っています。私は、それが "Resource Acquisition is Initialization" の略であると信じています。しかし、この名前は RAII が何であるかについての私の理解とは一致しません(おそらく間違っています)。RAII は、スタック上のオブジェクトを初期化する方法であり、それらの変数がスコープ外に出たときに、デストラクタが自動的に呼び出されてリソースがクリーンアップされるという印象を受けます。
では、なぜそれが "using the stack to trigger cleanup" (UTSTTC:) と呼ばれないのでしょうか? そこからどのようにして "RAII" に至るのでしょうか?
そして、ヒープに住んでいる何かのクリーンアップを引き起こすスタック上の何かをどのように作ることができますか?また、RAIIを使えないケースもあるのでしょうか?ガベージコレクションがあればと思うことはありませんか?少なくとも、あるオブジェクトにはガベージコレクタを使用し、他のオブジェクトは管理できるようにしたいと思ったことはありませんか?
ありがとうございます。
どのように解決するのですか?
<ブロッククオートでは、なぜ "using the stack to trigger cleanup" (UTSTTC:) と呼ばれないのでしょうか?
RAIIは何をすべきかを教えてくれているのです。コンストラクタでリソースを取得することです。1つのリソース、1つのコンストラクタ。UTSTTCはその1つの応用で、RAIIはそれ以上です。
リソース管理は最悪です。 ここで、リソースとは、使用後にクリーンアップが必要なものすべてを指します。多くのプラットフォームにわたるプロジェクトの研究では、バグの大部分はリソース管理に関連しており、Windows では特にひどい (多くの種類のオブジェクトとアロケータがあるため) ことが分かっています。
C++では、例外と (C++ スタイルの) テンプレートの組み合わせにより、リソース管理は特に複雑になっています。フードの下を覗くには、以下を参照してください。 GOTW8 ).
C++では、デストラクタを呼び出す際に である場合にのみ であることを保証しています。これに依存して、RAIIは平均的なプログラマが気づかないような多くの厄介な問題を解決することができます。以下は、quot; my local variables will be destroyed whenever I return"を超えるいくつかの例です。
まず、あまりにも単純化された
FileHandle
クラスから始めてみましょう。
class FileHandle
{
FILE* file;
public:
explicit FileHandle(const char* name)
{
file = fopen(name);
if (!file)
{
throw "MAYDAY! MAYDAY";
}
}
~FileHandle()
{
// The only reason we are checking the file pointer for validity
// is because it might have been moved (see below).
// It is NOT needed to check against a failed constructor,
// because the destructor is NEVER executed when the constructor fails!
if (file)
{
fclose(file);
}
}
// The following technicalities can be skipped on the first read.
// They are not crucial to understanding the basic idea of RAII.
// However, if you plan to implement your own RAII classes,
// it is absolutely essential that you read on :)
// It does not make sense to copy a file handle,
// hence we disallow the otherwise implicitly generated copy operations.
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// The following operations enable transfer of ownership
// and require compiler support for rvalue references, a C++0x feature.
// Essentially, a resource is "moved" from one object to another.
FileHandle(FileHandle&& that)
{
file = that.file;
that.file = 0;
}
FileHandle& operator=(FileHandle&& that)
{
file = that.file;
that.file = 0;
return *this;
}
}
もし構築が(例外を伴って)失敗した場合、他のメンバー関数-デストラクタでさえ-は呼び出されることはありません。
RAIIは無効な状態のオブジェクトを使用しない。 オブジェクトを使用する前に、すでに生活を容易にしています。
では、テンポラリーオブジェクトについて見てみましょう。
void CopyFileData(FileHandle source, FileHandle dest);
void Foo()
{
CopyFileData(FileHandle("C:\\source"), FileHandle("C:\\dest"));
}
処理すべきエラーは、ファイルが開けない、1つのファイルしか開けない、両方のファイルを開くことができるがファイルのコピーに失敗した、の3つである。非RAIIの実装では
Foo
は 3 つのケースをすべて明示的に処理しなければならないでしょう。
RAIIは、1つのステートメント内で複数のリソースを取得した場合でも、取得したリソースを解放します。
では、いくつかのオブジェクトを集約してみましょう。
class Logger
{
FileHandle original, duplex; // this logger can write to two files at once!
public:
Logger(const char* filename1, const char* filename2)
: original(filename1), duplex(filename2)
{
if (!filewrite_duplex(original, duplex, "New Session"))
throw "Ugh damn!";
}
}
のコンストラクタは
Logger
は失敗します。
original
のコンストラクタが失敗した場合 (なぜなら
filename1
を開くことができなかったため)。
duplex
のコンストラクタは失敗します (なぜなら
filename2
が開けなかったため)、あるいは
Logger
のコンストラクタ本体内のファイルへの書き込みに失敗します。これらのケースのいずれにおいても
Logger
のデストラクタは
ではなく
が呼ばれることはありません。
Logger
のデストラクタがファイルを解放することに頼ることはできません。しかし、もし
original
が構築された場合、そのデストラクタは
Logger
コンストラクタのクリーンアップ中に呼び出されます。
RAIIは部分的な構築後の後始末を簡素化します。
否定的な点
マイナスポイント?全ての問題はRAIIとスマートポインタで解決できる;-)
RAIIは、遅延取得が必要な場合、集約されたオブジェクトをヒープに押し付けるため、扱いにくいことがあります。
ロガーが
SetTargetFile(const char* target)
. その場合、ハンドルは、やはり
Logger
のメンバーである必要があり、ヒープ上に存在する必要があります (例えば、スマートポインターで、ハンドルの破壊を適切にトリガーするためです)。
私はガベージコレクションを本当に望んだことはありません。C# を使用しているとき、私は時々、気にする必要がないという至福の瞬間を感じることがありますが、それ以上に、決定論的破壊によって作成できるすべてのクールなおもちゃが恋しくなります。(使用方法
IDisposable
を使うだけではダメなのです)。
私は、GC の恩恵を受けるかもしれない、特に複雑な構造を 1 つ持っていました。そこでは、単純なスマート ポインターが複数のクラスにわたって循環参照を引き起こします。私たちは、強いポインターと弱いポインターのバランスを注意深くとることで乗り切りましたが、何かを変更したいときはいつでも、大きな関係図を研究しなければなりません。GC はより良かったかもしれませんが、いくつかのコンポーネントは、早急にリリースされるべきリソースを保持していました。
FileHandle サンプルについてのメモです。これは完全なものではなく、単なるサンプルでしたが、不正確であることが判明しました。指摘してくれた Johannes Schaub と、それを正しい C++0x ソリューションに変えてくれた FredOverflow に感謝します。時間をかけて、私は次のようなアプローチに落ち着きました。 ここに記載されている .
関連
-
[解決済み】Cygwin Make bash コマンドが見つかりません。
-
[解決済み】IntelliSense:オブジェクトに、メンバー関数と互換性のない型修飾子がある
-
[解決済み】C++プログラムでのコンソールの一時停止
-
[解決済み】Visual C++で "Debug Assertion failed "の原因となる行を見つける。
-
[解決済み】C++ - ステートメントがオーバーロードされた関数のアドレスを解決できない。
-
[解決済み] 数値定数の前にunqualified-idを付けて、数値を定義することを期待する。
-
[解決済み] 警告:暗黙の定数変換でのオーバーフロー
-
[解決済み] System.gc()を呼び出すのはなぜ悪い習慣なのですか?
-
[解決済み】なぜC++プログラマは'new'の使用を最小限に抑えなければならないのでしょうか?
-
[解決済み] C++のスタック、スタティック、ヒープ
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] テスト
-
[解決済み] エラーが発生する。ISO C++は型を持たない宣言を禁じています。
-
[解決済み】C++エラーです。"配列は中括弧で囲まれたイニシャライザーで初期化する必要がある"
-
[解決済み】C++でランダムな2倍数を生成する
-
[解決済み] 非常に基本的なC++プログラムの問題 - バイナリ式への無効なオペランド
-
[解決済み】クラスのコンストラクタへの未定義参照、.cppファイルの修正も含む
-
[解決済み】C++ - ステートメントがオーバーロードされた関数のアドレスを解決できない。
-
[解決済み】Visual Studioのデバッガーエラー。プログラムを開始できません 指定されたファイルが見つかりません
-
[解決済み] C++で配列はどのように使うのですか?
-
[解決済み】C++のRAIIとスマートポインタ