C++ での例外とスタック アンワインド
C++ 例外の機能では、制御は throw ステートメントから、スローされる型を処理できる最初の catch ステートメントに移動します。 catch ステートメントに到達すると、throw ステートメントおよび catch ステートメントの間のスコープ内のすべての自動変数は、スタック アンワインドと呼ばれるプロセスで破棄されます。 スタック アンワインドでは、次のように実行されます。
通常の順次実行によって制御が
try
ステートメントに到達します。try
ブロック内の保護されたセクションが実行されます。保護されたセクションの実行中に例外がスローされない場合、
try
ブロックに続くcatch
句は実行されません。 実行は、関連付けられたtry
ブロックに続く最後のcatch
句の後のステートメントに移って続行されます。保護されたセクションの実行中または保護されたセクションで直接または間接的に呼び出される任意のルーチンで例外がスローされると、例外オブジェクトは
throw
オペランドで作成されたオブジェクトから作成されます (これは、コピー コンストラクターが関係する可能性があることを意味します)。この時点で、コンパイラは、スローされた型の例外を処理できる上位の実行コンテキストでcatch
句を検索するか、任意の種類の例外を処理できるcatch
ハンドラーを検索します。catch
ハンドラーは、try
ブロックの後で、記述した順序でチェックされます。 適切なハンドラーが見つからない場合、次の動的に囲まれているtry
ブロックが調べられます。 このプロセスは、最も外側を囲んでいるtry
ブロックがチェックされるまで続行されます。一致するハンドラーが見つからない場合、またはアンワインド処理中に例外が発生した場合でも、ハンドラーが制御を取得する前であれば、定義済みのランタイム関数
terminate
が呼び出されます。 例外がスローされた後でも、アンワインドが開始される前に例外が発生した場合は、terminate
が呼び出されます。一致している
catch
ハンドラーが見つかり、そのハンドラーが値でキャッチする場合、その仮パラメーターは例外オブジェクトをコピーして初期化されます。 参照によってキャッチする場合、例外オブジェクトを示すためにパラメーターが初期化されます。 正式なパラメーターが初期化されると、スタックをアンワインドするプロセスが開始されます。 これには、catch
ハンドラーに関連付けられたtry
ブロックの開始から例外のスロー サイトまでの間に完全に構築された (まだ破棄されていない) すべての自動オブジェクトの破棄が含まれます。 破棄は、構築の逆順に発生します。catch
ハンドラーが実行され、最後のハンドラー (つまり、catch
ハンドラーではない最初のステートメントまたは構造体) の後で、プログラムが実行を再開します。catch
ハンドラーに制御を入れることができるのはスローされた例外による場合だけで、goto
ステートメント、またはswitch
ステートメントのcase
ラベルによって入れることはできません。
スタック アンワインドの例
次の例では、例外がスローされたときにスタックをアンワインドする方法を示します。 スレッド上で実行すると、C
の throw ステートメントから main
の catch ステートメントにジャンプし、その途中の各関数をアンワインドします。 Dummy
オブジェクトが作成され、スコープ外として破棄される順序に注意してください。 関数が catch ステートメントを含む main
なしで完了しないことにも注意してください。 関数 A
はその B()
の呼び出しから戻りません。B
はその C()
の呼び出しから戻りません。 Dummy
ポインターの定義と対応する DELETE ステートメントをコメントから外してプログラムを実行すると、ポインターは削除されなくなります。 これは、関数が例外の保証を提供しない場合に発生することがある問題を示しています。 詳細については、「 方法: 例外の設計」を参照してください。 catch ステートメントをコメント アウトすると、未処理の例外によってプログラムが終了するときの動作が示されます。
#include <string>
#include <iostream>
using namespace std;
class MyException{};
class Dummy
{
public:
Dummy(string s) : MyName(s) { PrintMsg("Created Dummy:"); }
Dummy(const Dummy& other) : MyName(other.MyName){ PrintMsg("Copy created Dummy:"); }
~Dummy(){ PrintMsg("Destroyed Dummy:"); }
void PrintMsg(string s) { cout << s << MyName << endl; }
string MyName;
int level;
};
void C(Dummy d, int i)
{
cout << "Entering FunctionC" << endl;
d.MyName = " C";
throw MyException();
cout << "Exiting FunctionC" << endl;
}
void B(Dummy d, int i)
{
cout << "Entering FunctionB" << endl;
d.MyName = "B";
C(d, i + 1);
cout << "Exiting FunctionB" << endl;
}
void A(Dummy d, int i)
{
cout << "Entering FunctionA" << endl;
d.MyName = " A" ;
// Dummy* pd = new Dummy("new Dummy"); //Not exception safe!!!
B(d, i + 1);
// delete pd;
cout << "Exiting FunctionA" << endl;
}
int main()
{
cout << "Entering main" << endl;
try
{
Dummy d(" M");
A(d,1);
}
catch (MyException& e)
{
cout << "Caught an exception of type: " << typeid(e).name() << endl;
}
cout << "Exiting main." << endl;
char c;
cin >> c;
}
/* Output:
Entering main
Created Dummy: M
Copy created Dummy: M
Entering FunctionA
Copy created Dummy: A
Entering FunctionB
Copy created Dummy: B
Entering FunctionC
Destroyed Dummy: C
Destroyed Dummy: B
Destroyed Dummy: A
Destroyed Dummy: M
Caught an exception of type: class MyException
Exiting main.
*/