リソース取得が初期化(RAII)とはどういう意味ですか?
リソース取得が初期化(RAII)とはどういう意味ですか?
回答:
これは信じられないほど強力なコンセプトの本当にひどい名前であり、おそらく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();
この後者の場合、例外がスローされてスタックがアンワインドされると、ローカル変数が破棄され、リソースが確実にクリーンアップされ、リークしなくなります。
Scope-Bound
ストレージクラス指定子とスコープを組み合わせてエンティティのストレージ期間を決定するため、ここでが最適な名前の選択かどうかはわかりません。スコープ境界にそれを
これはプログラミングのイディオムであり、簡単に言うと
これにより、リソースの使用中に何が起こっても、最終的に解放されます(通常の戻り、含まれているオブジェクトの破棄、またはスローされた例外にかかわらず)。
これは、リソースを安全に処理する方法であるだけでなく、エラー処理コードとメイン機能を混在させる必要がないため、コードをよりクリーンにするため、C ++で広く使用されている優れた手法です。
*
更新:「ローカル」は、ローカル変数、またはクラスの非静的メンバー変数を意味する場合があります。後者の場合、メンバー変数はその所有者オブジェクトで初期化および破棄されます。
**
Update2: @sbiが指摘したように、リソースは、多くの場合コンストラクター内に割り当てられますが、外部に割り当てられ、パラメーターとして渡されることもあります。
open()
/ close()
メソッドがなく、コンストラクタとデストラクタだけがあることを意味していると思います。したがって、リソースの「保持」は、オブジェクトのライフタイムにすぎません。コンテキストで処理(スタック)または明示的に(動的割り当て)
「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つのポインターのみが所有しており、オブジェクトをコピーしようとすると、(構文ハッカーを通じて)オブジェクトの所有権がコピー操作のターゲットに転送されます。
std::auto_ptr
の廃止バージョンですstd::unique_ptr
。std::auto_ptr
一種のシミュレートされた移動セマンティクスは、C ++ 98で可能な限り、std::unique_ptr
C ++ 11の新しい移動セマンティクスを使用します。C ++ 11の移動のセマンティクスがより明示的であるため(std::move
一時的なものを除いて必要)、新しいクラスが作成されましたstd::auto_ptr
。
オブジェクトの存続期間は、そのスコープによって決まります。ただし、オブジェクトが作成されたスコープとは無関係に存続するオブジェクトを作成する必要がある場合や、オブジェクトを作成したい場合があります。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_ptr
、std::unique_ptr
とstd::lock_guard
。
この手法の別名はSBRMで、Scope-Bound Resource Managementの略です。
著書 『C ++ Programming with Design Patterns Revealed』では、RAIIを次のように説明しています。
どこ
リソースはクラスとして実装され、すべてのポインターはその周りにクラスラッパーを持っています(スマートポインターになります)。
リソースは、コンストラクターを呼び出すことによって取得され、デストラクタを呼び出すことによって暗黙的に(取得の逆順で)解放されます。
RAIIクラスには3つの部分があります。
RAIIは「リソースの取得は初期化です」の略です。RAIIの「リソース取得」の部分では、後で終了する必要がある何かを開始します。
「初期化あり」の部分は、取得がクラスのコンストラクター内で行われることを意味します。
手動メモリ管理は、コンパイラの発明以来、プログラマが回避する方法を発明してきた悪夢です。ガベージコレクターを備えたプログラミング言語を使用すると、作業は簡単になりますが、パフォーマンスは低下します。この記事- ガベージコレクターの排除:RAIIウェイ、ToptalエンジニアのPeter Goodspeed-Niklausがガベージコレクターの歴史をのぞき、所有権と借用の概念が安全性の保証を損なうことなくガベージコレクターを排除するのにどのように役立つかを説明します。