リソース獲得とは、初期化(RAII)とはどういう意味ですか?
これは、信じられないほど強力なコンセプトの非常に恐ろしい名前であり、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();
後者の場合、例外がスローされてスタックが巻き戻されると、ローカル変数が破棄されるため、リソースがクリーンアップされ、リークしなくなります。
これはプログラミングのイディオムであり、簡単に言うと
これにより、リソースの使用中に何が起こっても、最終的には解放されます(通常の戻り、収容オブジェクトの破壊、またはスローされた例外)。
これは、C++で広く使用されている優れた方法です。リソースを安全に処理する方法であるだけでなく、エラー処理コードを主要機能と混在させる必要がないため、コードがよりクリーンになります。
*
pdate: "local"は、ローカル変数、またはクラスの非静的メンバー変数を意味する場合があります。後者の場合、メンバー変数はその所有者オブジェクトで初期化および破棄されます。
**
pdate2: @sbiが指摘したように、リソースは(多くの場合コンストラクター内に割り当てられますが)外部にも割り当てられ、パラメーターとして渡されます。
「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つのポインターのみによって所有され、オブジェクトをコピーしようとすると、(構文ハッカーによって)所有権が転送されますコピー操作のターゲットへのオブジェクトの。
C++ Programming with Design Patterns Revealed という本は、RAIIを次のように説明しています。
どこ
リソースはクラスとして実装され、すべてのポインターにはクラスラッパーがあります(スマートポインターになります)。
リソースは、コンストラクターを呼び出すことによって獲得され、デストラクターを呼び出すことによって暗黙的に(獲得の逆順で)リリースされます。
オブジェクトの有効期間は、そのスコープによって決まります。ただし、作成されたスコープとは無関係に存続するオブジェクトを作成することが必要な場合、または有用な場合があります。 C++では、演算子new
を使用してこのようなオブジェクトを作成します。オブジェクトを破棄するには、演算子delete
を使用できます。演算子new
によって作成されたオブジェクトは動的に割り当てられます。つまり、動的メモリに割り当てられます(heapまたは無料ストア)。したがって、new
で作成されたオブジェクトは、delete
を使用して明示的に破棄されるまで存在し続けます。
new
およびdelete
を使用するときに発生する可能性のある間違いは次のとおりです。
new
を使用してオブジェクトを割り当て、delete
オブジェクト。delete
オブジェクトを使用し、他のポインターを使用します。delete
オブジェクトを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_ptr
、std::unique_ptr
、およびstd::lock_guard
です。
この手法の別の名前はSBRM、Scope-Bound Resource Managementの略です。
手動のメモリ管理は、コンパイラが発明されて以来、プログラマが回避する方法を発明してきた悪夢です。ガベージコレクターを使用したプログラミング言語は、パフォーマンスを犠牲にしますが、生活を楽にします。この記事では、 ガベージコレクターの排除:RAII Way 、ToptalエンジニアのPeter Goodspeed-Niklausがガベージコレクターの歴史を覗き、所有権と借用の概念がガベージコレクターを排除するのにどのように役立つかを説明します安全性の保証を損なう。
RAIIクラスには3つの部分があります。
RAIIは「リソースの取得は初期化」を意味します。 RAIIの「リソース獲得」の部分は、次のような、終了する必要がある何かを開始する場所です。
「初期化」部分は、クラスのコンストラクター内で取得が行われることを意味します。