ikautak.log

C/C++, Python, CUDA, Android, Linux kernel, Network, etc.

More Effective C++ 項目12 例外発生の仕方と引数引渡しや仮想関数呼び出しとの違いを理解する

新訂版 More Effective C++ (AddisonーWesley professional co)

新訂版 More Effective C++ (AddisonーWesley professional co)

  • 作者: スコット・メイヤーズ,安村通晃,伊賀聡一郎,飯田朱美,永田周一
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2007/06/29
  • メディア: 単行本(ソフトカバー)
  • 購入: 8人 クリック: 129回
  • この商品を含むブログ (43件) を見る

項目12 例外発生の仕方と引数引渡しや仮想関数呼び出しとの違いを理解する

関数の宣言とcatch節の構文規則は似ている。

class Widget {...};

void f1(Widget w);
void f2(Widget& w);
void f3(const Widget& w);
void f4(Widget* pw);
void f5(const Widget* pw);

catch (Widget w) ...
catch (Widget& w) ...
catch (const Widget& w) ...
catch (Widget* pw) ...
catch (const Widget* pw) ...

例外をthrow側からcatch節へ引き渡すことは、実引数を関数呼び出し側から関数側へ渡す方法と同じと思うかも知れないが、重大な相違点もある。

関数引数も例外も、値渡し、参照渡し、ポインタ渡しのいずれも可能。
しかし、関数と違って例外はthrow側に制御が戻ってこないため、相違点がある。

// ストリームからWidgetの値を読む関数
istream operator>>(istream& s, Widget& w);
void passAndThrowWidget() {
    Widget localWidget;
    cin >> localWidget;  // localWidgetをoperator>>に渡す
    throw localWidget;   // localWidgetを例外として投げる
}

このとき、operator>>にはWidgetの参照が渡される。
しかし、例外が投げられると、スコープの外に出るため、catch側が値として受けとろうが、参照として受け取ろうがコピーが渡される。

staticでもコピーが渡される。

void passAndThrowWidget() {
    static Widget localWidget; // staticなのでプログラムの終了まで存在する
    cin >> localWidget;
    throw localWidget;  // コピーが例外として投げられる
}


例外オブジェクトの強制コピーは、そのオブジェクトのコピーコンストラクタによって行われる。
コピーコンストラクタはオブジェクトの静的型対応したコピーコンストラクタが使用される。

class Widget {...};
class SpecialWidget : public Widget {...};
void passAndThrowWidget() {
    SpecialWidget localSpecialWidget;
    ...
    Widget& rw = localSpecialWidget;

    throw rw;  // 実際にはSpecialWidgetを参照していようが、
               // Widgetクラスのコピーコンストラクタが使われる
}

実際に参照しているのがSpecialWidgetでも、静的な型のWidget例外が発生する。

catchブロックでの伝搬のさせ方でも変わってくる。

catch (Widget& w) {
  ...
  throw;  // 既にコピーされた例外を伝搬
}

catch (Widget& w) {
  ...
  throw w;  // 元が何であろうとWidget型のコピーを投げる
}

最初のブロックは現在の例外をそのまま投げるため、再度コピーされることはない。最初に発生したのがSpecialWidget例外であれば、たとえcatchブロックがWidget型でも、SpecialWidget例外が伝搬していく。
下のブロックは新しい例外を投げているが、その型がWidgetなのでWidget型のコピーが伝搬する。


ポインタ渡しの例外はポインタのコピーが渡されるので、ローカルオブジェクトを指し示すポインタを投げてはいけない。例外を処理するときにはローカルなオブジェクトは破棄されている。


例外の型をcatchにマッチさせるときの変換は2種類ある。

  1. 継承をベースにした変換
  2. 型付きポインタから型なしポインタ

基底クラス例外に対するcatch節は、派生クラスの例外も処理することができる。

exception
↑
logic_error
↑
invalid_argment

logic_errorに対するcatch節は、invalid_argmentの例外も処理できる。

2つ目は型付きポインタから型なしポインタへの変換であり、const void*を受け取るcatch節は、どんなポインタ型の例外も受け取る。

関数の引数渡しと例外の伝搬は、適合する処理の探し方も異なる。
仮想関数は動的に最も近いクラスの関数が呼び出されるが、catch節は先頭一致のアルゴリズムで、ソースコード順に決められる。

まとめ
例外オブジェクトは常にコピーされる。値として受け取ると2度コピーされる。
例外オブジェクトは関数の引数より少ない変換に従う。catch節はソースコード順で適合する場所が実行される。