RAIIおよび未処理の例外

確かに誰もが素晴らしいRAII方法論に関する大文字(C ++に関する書籍)の真実を知っています。そうでない場合は、ウィキペディアから簡単な説明をします。



これは、この手法のテンプレート記述です。
リソースの取得は初期化(Eng。Resource Acquisition Is Initialization(RAII))-オブジェクト指向プログラミングのソフトウェアイディオムであり、その意味は、さまざまなソフトウェアメカニズムの助けを借りて、リソースの取得が初期化と密接に組み合わされ、リリース-破壊オブジェクト。



典型的な(唯一ではありませんが)実装方法は、コンストラクターでリソースへのアクセスを整理し、対応するクラスのデストラクターで解放することです。 自動変数のデストラクターはスコープを出るときに呼び出されるため、変数が破棄されるとリソースが解放されることが保証されます。 これは、例外が発生する状況にも当てはまります。 これにより、RAIIはプログラミング言語で例外セーフコードを記述するための重要な概念となり、自動オブジェクトのコンストラクタとデストラクタが主にC ++で自動的に呼び出されます。



最後の文は結果の100%の保証を約束しているように見えますが、これまでと同じく、特にC ++では、ニュアンスがあります。



RAIIを使用したコード例:



ネットワークアクセスをカプセル化するクラスがあるとしましょう。



class Network{ public: Network(const URL &url) : m_url(url) { } private: Url m_url; };
      
      





RAIIを実装するクラスを作成します。



 class LockNet{ public: LockNet(const Url &url){ m_net = new Network(url); } ~LockNet (){ delete m_net; } operator Network * (){ return network; } private: Network *m_net; };
      
      





これで、メイン関数でこのリソースを安全に使用できます。



 int main(int argc, char *argv[]) { LockNet net("http://habrahabr.ru") // -  , //    return 0; }
      
      





RAIIが約束しているように、例外がスローされても、LockNetクラスのm_netポインターは正しく削除されます。 そうだね



ああ、いや。



何らかの理由で、RAIIの説明では、通常、この手法が機能するために、このタイプの例外ハンドラーで例外をキャッチする必要があることを記述することを忘れています。そうでない場合、ハンドラーが見つからない場合、std :: terminate()が呼び出され、プログラムがクラッシュします Straustrupは、C ++プログラミング言語(03)の14.7章でこのことを説明しています。



ローカルオブジェクトの削除は実装によって異なります。どこで削除されるか、またはその逆です。そのため、開発者は、コアダンプを読み込むデバッガーで、例外の時点でローカルオブジェクトの状態を確認できます。 また、ローカルオブジェクトを確実に削除する必要がある場合は、例外をキャッチするtry-catch(...)ブロックでメイン関数のコードをラップすることをお勧めします。



T.ch. メイン関数コードで、戻り値0;演算子の前に例外がある場合、通常のリソースリークが発生します。

OSはコアダンプを保存し、プログラムが占有しているリソースを解放するため、致命的ではありません。



これを確認する方法は? 確認コードを書く!



このコードでは、RAII手法を実装するスマートポインターを使用します。



 #include <iostream> #include <vector> #include <memory> #include <string> #include <exception> using namespace std; class MyExc { }; class Slot { public: Slot(const std::string &str = "NONAME") : m_name(str) { cout << "Constructor of slot: " << m_name << endl; } virtual ~Slot() { cout << "Destructor of slot: " << m_name << endl; } void sayName() { cout << "Slot name is: " << m_name << endl; throw MyExc(); } private: string m_name; }; void testShared(shared_ptr<Slot> & m_share) { m_share->sayName(); } int main() { vector<shared_ptr<Slot>> vec {make_shared<Slot>("0"), make_shared<Slot>("1"), make_shared<Slot>("2"), make_shared<Slot>("3"), make_shared<Slot>("4")}; for (auto& x:vec) testShared(x); return 0; }
      
      





このプログラムをコンパイルして実行すると、結論が得られます。



スロットのコンストラクター:0

「MyExc」のインスタンスをスローした後に呼び出された終了

スロットのコンストラクター:1

スロットのコンストラクター:2

スロットのコンストラクター:3

スロットのコンストラクター:4

スロット名:0


メイン関数を書き換え、try-catchブロックで例外をスローする関数呼び出しをラップします。



 int main() { try { vector<shared_ptr<Slot>> vec{make_shared<Slot>("0"), make_shared<Slot>("1"), make_shared<Slot>("2"), make_shared<Slot>("3"), make_shared<Slot>("4")}; for (auto &x:vec) testShared(x); } catch (std::exception & ex){ cout<<ex.what()<<endl; } catch (...){ cout<<"Unexpected exception"<<endl; }
      
      





そして出来上がり-すべてが正常に動作し始めます。



コンストラクターだけでなく、スマートポインターに格納されているオブジェクトのデストラクターも呼び出されます。



プログラム出力:



スロットのコンストラクター:0

スロットのコンストラクター:1

スロットのコンストラクター:2

スロットのコンストラクター:3

スロットのコンストラクター:4

スロット名:0

予期しない例外

スロットのデストラクター:0

スロットのデストラクター:1

スロットのデストラクター:2

スロットのデストラクター:3

スロットのデストラクター:4





ただし、すべては標準に準拠しています。



15.2コンストラクタとデストラクタ



1. throw-expressionからハンドラーに制御が渡されると、すべての自動オブジェクトに対してデストラクターが呼び出されます

tryブロックに入った後に構築されました。 自動オブジェクトは、逆の順序で破棄されます

建設の完了。



3. tryブロックからaへのパス上に構築された自動オブジェクトのデストラクタを呼び出すプロセス

throw-expressionは「スタックの巻き戻し」と呼ばれます。スタックの巻き戻し中に呼び出されたデストラクタが、

例外、std :: terminateが呼び出されます(15.5.1)。



15.3例外の処理



9一致するハンドラが見つからない場合、関数std :: terminate()が呼び出されます。 スタックが

このstd :: terminate()の呼び出しの前に巻き戻されるのは実装定義(15.5.1)です。



g ++(Ubuntu 4.9.2-0ubuntu1〜14.04)4.9.2、Visual Studio 2013 CEでテスト済み。



参照資料



RAII

ISO C ++



All Articles