More C++ Idioms/生成の追跡(Construction Tracker)
生成の追跡(Construction Tracker)
[編集]意図
[編集]コンストラクタの初期化リスト中で同じ型の例外を送出しうる 2 つ以上のオブジェクトを初期化する際に、例外を送出したデータメンバを識別する。
別名
[編集]動機
[編集]コンストラクタの初期化リスト中で 2 つ以上のオブジェクトを初期化し、その全てが同じ例外(std::exception) を送出しうる場合を考えよう。この際、初期化リストの周りには、ただ 1 つの try ブロックしか置けないため、どのオブジェクトの構築が失敗したかを追跡するのはやっかいな問題になる。そのような try ブロックは関数 try ブロック(function-try block)に過ぎないが、「コンストラクタ try ブロック」という特別な呼び名を持つ。
解法とサンプルコード
[編集]生成の追跡(Construction Tracker)イディオムは、初期化リスト中での正常なオブジェクトの構築を追跡するという単純なテクニックである。カウンタは、単純に、一つ一つのオブジェクトのコンストラクタが正常終了するごとにインクリメントされる。コンストラクタの呼び出し間に、クラスの利用者に見えない形でカウンタのインクリメントを割り込ませるために、括弧を賢く利用する。
#include <iostream>
#include <stdexcept>
struct B {
B (char const *) { throw std::runtime_error("B エラー"); }
};
struct C {
C (char const *) { throw std::runtime_error("C エラー"); }
};
class A
{
B b_;
C c_;
enum TrackerType { NONE, ONE, TWO };
public:
A( TrackerType tracker = NONE)
try // コンストラクタ try ブロック(constructor try block)
: b_((tracker = ONE, "hello")) // std::exception を送出しうる
, c_((tracker = TWO, "world")) // std::exception を送出しうる
{
assert(tracker == TWO);
// ... コンストラクタ本体 ...
}
catch (std::exception const & e)
{
if (tracker == ONE) {
{
std::cout << "B が例外送出: " << e.what() << std::endl;
}
else if (tracker == TWO) {
std::cout << "C が例外送出: " << e.what() << std::endl;
}
throw;
}
};
int main (void)
{
try {
A a;
}
catch (std::exception const & e) {
std::cout << "例外捕捉: " << e.what() << std::endl;
}
return 0;
}
二重の括弧が、追跡用変数への代入を置くために括弧を使う方法である。 このイディオムは、B と C のコンストラクタが最低 1 つのパラメータを取るという点に決定的に依存している。もし、クラス B と C のコンストラクタがパラメータを取らない場合、ダミーパラメータを受け B と C のコンストラクタをデフォルトパラメータで呼び出すアダプタクラスのようなものを書く必要がある。そのようなアダプタは、下からの混入(mixin-from-below)テクニックを用いる、パラメタ化された基本クラス(Parameterized Base Class)イディオムによって書くことが出来る。アダプタクラスはまた、クラス A 中に完全にカプセル化することも出来る。クラス A のコンストラクタ中で、追跡用変数はデフォルト値を持つパラメータとなっているため、ユーザ側が気にかけることはない。また、private な enum 型とすることにより、本来と異なるパラメータによってコンストラクタが呼び出されることが無いよう保証している。