リソース取得が初期化(RAII)とはどういう意味ですか?


283

リソース取得が初期化(RAII)とはどういう意味ですか?



13
これは私にとってそれを家に追いやるものです。 stroustrup.com/bs_faq2.html#finally
Hal Canary

2
3つの文と2つの例を含むMicrosoftのリファレンスですが、非常に明確です。msdn.microsoft.com/en-us/library/hh438480.aspx
Gab是好人

回答:


374

これは信じられないほど強力なコンセプトの本当にひどい名前であり、おそらくC ++開発者が他の言語に切り替えるときに見逃してしまう最大のことの1つでしょう。この概念の名前をScope-Bound Resource Managementに変更しようとする動きが少しありましたまだまだ普及していないようです。

「リソース」とは、単なるメモリを意味するのではなく、ファイルハンドル、ネットワークソケット、データベースハンドル、GDIオブジェクトなどのこともあります。それらの使用を制御します。'Scope-bound'アスペクトは、オブジェクトの存続期間が変数のスコープにバインドされることを意味します。そのため、変数がスコープから外れると、デストラクタがリソースを解放します。これの非常に便利な特性は、例外の安全性を高めることです。たとえば、これを比較します。

RawResourceHandle* handle=createNewResource();
handle->performInvalidOperation();  // Oops, throws exception
...
deleteResource(handle); // oh dear, never gets called so the resource leaks

RAII one

class ManagedResourceHandle {
public:
   ManagedResourceHandle(RawResourceHandle* rawHandle_) : rawHandle(rawHandle_) {};
   ~ManagedResourceHandle() {delete rawHandle; }
   ... // omitted operator*, etc
private:
   RawResourceHandle* rawHandle;
};

ManagedResourceHandle handle(createNewResource());
handle->performInvalidOperation();

この後者の場合、例外がスローされてスタックがアンワインドされると、ローカル変数が破棄され、リソースが確実にクリーンアップされ、リークしなくなります。


2
@the_mandrill:私はideone.com/1Jjzucこのプログラムを試しました。しかし、デストラクタ呼び出しはありません。tomdalling.com/blog/software-design/…によれば、C ++は、例外がスローされた場合でも、スタック上のオブジェクトのデストラクタが呼び出されることを保証します。では、なぜデストラクタはここで実行しなかったのですか?リソースはリークされますか、それとも解放または解放されることはありませんか?
デストラクタ

8
例外がスローされますが、それをキャッチしていないため、アプリケーションが終了します。try {} catch(){}でラップすると、期待どおりに機能します:ideone.com/xm2GR9
the_mandrill

2
Scope-Boundストレージクラス指定子スコープを組み合わせてエンティティのストレージ期間を決定するため、ここでが最適な名前の選択かどうかはわかりません。スコープ境界にそれを
絞り込む

125

これはプログラミングのイディオムであり、簡単に言うと

  • リソースをクラスにカプセル化します(そのコンストラクターは通常-必ずしもではないが**-リソースを取得し、そのデストラクタは常にリソースを解放します)
  • クラスのローカルインスタンスを介してリソースを使用する*
  • オブジェクトがスコープ外になると、リソースは自動的に解放されます

これにより、リソースの使用中に何が起こっても、最終的に解放されます(通常の戻り、含まれているオブジェクトの破棄、またはスローされた例外にかかわらず)。

これは、リソースを安全に処理する方法であるだけでなく、エラー処理コードとメイン機能を混在させる必要がないため、コードをよりクリーンにするため、C ++で広く使用されている優れた手法です。

* 更新:「ローカル」は、ローカル変数、またはクラスの非静的メンバー変数を意味する場合があります。後者の場合、メンバー変数はその所有者オブジェクトで初期化および破棄されます。

** Update2: @sbiが指摘したように、リソースは、多くの場合コンストラクター内に割り当てられますが、外部に割り当てられ、パラメーターとして渡されることもあります。


1
私の知る限り、頭字語はオブジェクトがローカル(スタック)変数上にある必要があることを意味しません。別のオブジェクトのメンバー変数である可能性があるため、「保持」オブジェクトが破棄されると、メンバーオブジェクトも破棄され、リソースが解放されます。実際、頭字語は具体的には、リソースを初期化して解放するopen()/ close()メソッドがなく、コンストラクタとデストラクタだけがあることを意味していると思います。したがって、リソースの「保持」は、オブジェクトのライフタイムにすぎません。コンテキストで処理(スタック)または明示的に(動的割り当て)
Javier

1
実際には、リソースをコンストラクターで取得する必要があるということは何もありません。ファイルストリーム、文字列、他のコンテナーがこれを行いますが、スマートポインターの場合と同様に、リソースもコンストラクターに渡される可能性があります。あなたの答えが最も支持されている答えなので、これを修正したいかもしれません。
sbi 2012年

これは頭​​字語ではなく、略語です。IIRCのほとんどの人はそれを「ar ey ay ay」と発音しているので、say DARPAのような頭字語のスペルはありません。また、RAIIは単なるイディオムではなくパラダイムだと思います。
dtech 2013

@Peter Torok:ideone.com/1Jjzucこのプログラムを試しました。しかし、デストラクタ呼び出しはありません。tomdalling.com/blog/software-design/...は、例外がスローされた場合でも、C ++の保証がスタック上のオブジェクトのデストラクタが呼び出されることを言います。では、なぜデストラクタはここで実行しなかったのですか?リソースはリークされますか、それとも解放または解放されることはありませんか?
デストラクタ

50

「RAII」は「Resource Acquisition is Initialization」を意味し、関係するリソースの取得(およびオブジェクトの初期化)ではなく、リソースの解放破壊による)ではないため、実際にはかなり間違った名前です。オブジェクト)。
しかし、RAIIは私たちが得た名前であり、それは定着しています。

本質的に、このイディオムは、リソース(メモリのチャンク、開いているファイル、ロック解除されたミューテックス、you-name-it)をローカルの自動オブジェクトにカプセル化し、そのオブジェクトのデストラクタが、オブジェクトがそれが属するスコープの終わり:

{
  raii obj(acquire_resource());
  // ...
} // obj's dtor will call release_resource()

もちろん、オブジェクトは常にローカルな自動オブジェクトであるとは限りません。クラスのメンバーになることもできます。

class something {
private:
  raii obj_;  // will live and die with instances of the class
  // ... 
};

そのようなオブジェクトがメモリを管理する場合、それらはしばしば「スマートポインタ」と呼ばれます。

これには多くのバリエーションがあります。たとえば、最初のコードスニペットでは、誰かがコピーしたい場合にどうなるかという質問が生じますobj。最も簡単な方法は、単にコピーを禁止することです。std::unique_ptr<>これは、次のC ++標準で特徴付けられる標準ライブラリの一部となるスマートポインタです。
別のそのようなスマートポインターstd::shared_ptrは、リソースが保持するリソース(動的に割り当てられたオブジェクト)の「共有所有権」を備えています。つまり、自由にコピーでき、すべてのコピーが同じオブジェクトを参照します。スマートポインターは、同じオブジェクトを参照するコピーの数を追跡し、最後のコピーが破棄されるときに削除します。
3番目のバリアントは、std::auto_ptr 一種の移動セマンティクスを実装します。オブジェクトは1つのポインターのみが所有しており、オブジェクトをコピーしようとすると、(構文ハッカーを通じて)オブジェクトの所有権がコピー操作のターゲットに転送されます。


4
std::auto_ptrの廃止バージョンですstd::unique_ptrstd::auto_ptr一種のシミュレートされた移動セマンティクスは、C ++ 98で可能な限り、std::unique_ptrC ++ 11の新しい移動セマンティクスを使用します。C ++ 11の移動のセマンティクスがより明示的であるため(std::move一時的なものを除いて必要)、新しいクラスが作成されましたstd::auto_ptr
Jan Hudec 2013

@JiahaoCai:かつて何年も前(Usenet)で、Stroustrup氏はそう言っていました。
sbi

21

オブジェクトの存続期間は、そのスコープによって決まります。ただし、オブジェクトが作成されたスコープとは無関係に存続するオブジェクトを作成する必要がある場合や、オブジェクトを作成したい場合があります。C ++では、このnewようなオブジェクトを作成するために演算子が使用されます。そして、オブジェクトを破壊するために、演算子deleteを使用することができます。オペレーターによって作成されたオブジェクトnewは動的に割り当てられます。つまり、動的メモリ(ヒープまたはフリーストアとも呼ばれます)に割り当てられます。したがって、によって作成されたオブジェクトは、newを使用して明示的に破棄されるまで存在し続けdeleteます。

使用する際に発生する可能性がありますいくつかのミスnewとはdelete、次のとおりです。

  • 漏えいしたオブジェクト(またはメモリ):newオブジェクトを割り当て、オブジェクトに忘れるために使用deleteします。
  • 早期削除(またはダングリングリファレンス):オブジェクトへの別のポインターを保持し、deleteそのオブジェクトを使用して、別のポインターを使用します。
  • 二重削除deleteオブジェクトを2回試行します。

通常、スコープ付き変数が推奨されます。しかし、RAIIは、の代替として使用することができますnewし、deleteライブ独立してその範囲のオブジェクトを作成します。このような手法は、ヒープに割り当てられたオブジェクトへのポインターを取得し、それをハンドル/マネージャーオブジェクトに配置することで構成されます。後者には、オブジェクトの破棄を処理するデストラクタがあります。これにより、オブジェクトへのアクセスを必要とするすべての関数でオブジェクトが使用可能であり、ハンドルオブジェクトの存続期間中にオブジェクトが破棄されます明示的なクリーンアップを行わなくて終了。

RAIIを使用するC ++標準ライブラリの例は、std::stringおよびstd::vectorです。

次のコードを検討してください。

void fn(const std::string& str)
{
    std::vector<char> vec;
    for (auto c : str)
        vec.push_back(c);
    // do something
}

ベクトルを作成し、それに要素をプッシュするとき、そのような要素の割り当てや割り当て解除を気にする必要はありません。ベクターはnew、ヒープ上の要素にスペースを割り当て、そのスペースdeleteを解放するために使用します。Vectorのユーザーとして、実装の詳細は気にせず、Vectorがリークしないことを信頼します。この場合、ベクトルはハンドルオブジェクトですその要素のです。

標準ライブラリからの他の例は、使用RAIIはあることをstd::shared_ptrstd::unique_ptrstd::lock_guard

この手法の別名はSBRMでScope-Bound Resource Managementの略です。


1
「SBRM」は私にはもっと理にかなっています。RAIIは理解できたと思ったのでこの質問に行きましたが、その名前は私を騙していました。代わりに「スコープバインドリソース管理」と説明されているのを聞いて、私は本当に概念を理解していることにすぐに気づきました。
JShorthouse、

これがなぜこれが質問の回答としてマークされなかったのかはわかりません。それは感謝の@elmiomar、非常に徹底し、よく書かれた答えである
Abdelrahman Shoman

13

著書 『C ++ Programming with Design Patterns Revealed』では、RAIIを次のように説明しています。

  1. すべてのリソースを取得する
  2. リソースの使用
  3. リソースの解放

どこ

  • リソースはクラスとして実装され、すべてのポインターはその周りにクラスラッパーを持っています(スマートポインターになります)。

  • リソースは、コンストラクターを呼び出すことによって取得され、デストラクタを呼び出すことによって暗黙的に(取得の逆順で)解放されます。


1
@Brandin読者が著作権法の灰色の領域でフェアユースを構成するものについて議論するのではなく、重要なコンテンツに集中できるように投稿を編集しました。
Dennis

7

RAIIクラスには3つの部分があります。

  1. リソースはデストラクタで解放されます
  2. クラスのインスタンスはスタックに割り当てられます
  3. リソースはコンストラクタで取得されます。この部分はオプションですが、一般的です。

RAIIは「リソースの取得は初期化です」の略です。RAIIの「リソース取得」の部分では、後で終了する必要がある何かを開始します。

  1. ファイルを開く
  2. いくつかのメモリを割り当てる
  3. ロックの獲得

「初期化あり」の部分は、取得がクラスのコンストラクター内で行われることを意味します。

https://www.tomdalling.com/blog/software-design/resource-acquisition-is-initialization-raii-explained/


5

手動メモリ管理は、コンパイラの発明以来、プログラマが回避する方法を発明してきた悪夢です。ガベージコレクターを備えたプログラミング言語を使用すると、作業は簡単になりますが、パフォーマンスは低下します。この記事- ガベージコレクターの排除:RAIIウェイ、ToptalエンジニアのPeter Goodspeed-Niklausがガベージコレクターの歴史をのぞき、所有権と借用の概念が安全性の保証を損なうことなくガベージコレクターを排除するのにどのように役立つかを説明します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.