web-dev-qa-db-ja.com

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

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

235
John

これは、信じられないほど強力なコンセプトの非常に恐ろしい名前であり、C++開発者が他の言語に切り替えたときに見逃しているナンバー1の1つかもしれません。この概念の名前をScope-Bound Resource Managementに変更しようとする動きが少しありましたが、まだ理解されていないようです。

「リソース」と言うとき、メモリだけを意味するのではなく、ファイルハンドル、ネットワークソケット、データベースハンドル、GDIオブジェクトなどがあります。それらの使用を制御できる必要があります。 「スコープバウンド」の側面は、オブジェクトのライフタイムが変数のスコープにバインドされることを意味します。そのため、変数がスコープから外れると、デストラクタはリソースを解放します。これの非常に有用な特性は、例外安全性を高めることです。たとえば、これを比較してください:

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

RAIIで

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

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

後者の場合、例外がスローされてスタックが巻き戻されると、ローカル変数が破棄されるため、リソースがクリーンアップされ、リークしなくなります。

328
the_mandrill

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

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

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

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

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

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

113
Péter Török

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

基本的に、イディオムは、local、automatic objects =、およびオブジェクトが属するスコープの最後でオブジェクトが破棄されると、そのオブジェクトのデストラクタがリソースを解放します。

{
  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つのポインターのみによって所有され、オブジェクトをコピーしようとすると、(構文ハッカーによって)所有権が転送されますコピー操作のターゲットへのオブジェクトの。

45
sbi

C++ Programming with Design Patterns Revealed という本は、RAIIを次のように説明しています。

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

どこ

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

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

11
Dennis

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

newおよびdeleteを使用するときに発生する可能性のある間違いは次のとおりです。

  • リークしたオブジェクト(またはメモリ):newを使用してオブジェクトを割り当て、deleteオブジェクト。
  • 早すぎる削除(またはダングリングリファレンス):オブジェクトへの別のポインタを保持、deleteオブジェクトを使用し、他のポインターを使用します。
  • Double deletedeleteオブジェクトを2回試行しています。

一般的に、スコープ変数が優先されます。ただし、RAIIをnewおよびdeleteの代替として使用して、オブジェクトをそのスコープとは無関係に有効にすることができます。このような手法は、ヒープに割り当てられたオブジェクトへのポインターを取得し、それをhandle/managerオブジェクトに配置することで構成されます。後者には、オブジェクトの破壊を処理するデストラクタがあります。これにより、オブジェクトへのアクセスを必要とするすべての関数でオブジェクトが使用可能になり、handle objectのライフタイムが終了するとオブジェクトが破棄されます。明示的なクリーンアップの必要なし。

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を使用します。あなたはベクターのユーザーとして、実装の詳細を気にせず、ベクターがリークしないことを信頼します。この場合、ベクトルは要素のhandleオブジェクトです。

RAIIを使用する標準ライブラリの他の例は、std::shared_ptrstd::unique_ptr、およびstd::lock_guardです。

この手法の別の名前はSBRMScope-Bound Resource Managementの略です。

9
elmiomar

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

5
Dmitry Pavlov

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

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

RAIIは「リソースの取得は初期化」を意味します。 RAIIの「リソース獲得」の部分は、次のような、終了する必要がある何かを開始する場所です。

  1. ファイルを開く
  2. メモリを割り当てる
  3. ロックを取得する

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

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

5
Mohammad Moridi