例外の多段伝播

例外処理の一例

#include <iostream>
#include <string>
using namespace std;

class Exception {
    string str;
public:
    Exception() : str("Error0") {}
    Exception(const char* m) : str(m) {}
    void message() { cerr << str << endl; }
    void setmsg(const char* m) { str = m; }
};

int main()
{
    try {
        try {
            throw Exception("Error1"); // 例外オブジェクトをthrow
        } catch (Exception e) {  // 例外オブジェクトのコピーを受け取る
            e.message();
            e.setmsg("Error2");
            throw;  // オペランド無しでthrow
        }
    } catch (Exception& e) {  // 例外オブジェクトの参照を受け取る
        e.message();
    }
}

このコードを実行した場合

Error1
Error1

標準エラー出力に出力される。

catchブロック内でオペランド無しのthrowステートメントが実行された場合、そのcatchブロックにthrowされている例外オブジェクトが再throwされる。例外処理のこの仕様は知らなかった。

このように一度ハンドリングされた例外を上位モジュール(上位ブロック)に向かって再throwすることを「例外の多段伝播(Multi-level Propagation)」という。例外の多段伝播の有用な使用法としては汎的な例外ハンドラからの再throwだろうか。たとえば、

class exception1 : public exception {};
class exception2 : public exception {};
try {
    try {
        ...
    } catch (exception& e) {
        /* exception例外に対する処理 */
        throw;
    }
} catch (exception1& e) {
    /* exception1例外に対する処理 */
} catch (exception2& e) {
    /* exception2例外に対する処理 */
}

のような例外処理の構造の場合、2段目のtryブロック内でexception1,exception2のいずれかの例外がthrowされても、一旦は基底例外クラスexception型のcatchブロックでハンドリングされた後、exception1用,exception2用それぞれ専用のcatchブロックで再度ハンドリングすることができる。

デフォルト例外ハンドラでも同様で、

try {
    ...
} catch (...) {
   /* 未知の例外全てに対する処理 */
   throw;
}

とすることで、catchされた未知の例外を再throwすることができる。

このように多段伝播を使えば、下位モジュール内部で例外を汎的に処理しても、例外情報の粒度を変えることなく上位モジュールに対して例外の発生を知らせることができる。

モジュールが投げる例外は明確であること、例外の発行はひとつ上の呼び出し元のモジュールに向かってのみ行うもの、という姿勢に立つと例外の多段伝播は褒められたものではないとされる。それでも、C++において例外の多段伝播を良しとするところはいかにもC++らしい。その設計思想は、C++ D&Eの例外処理の章にも記されている。