ダウンキャスト再論
次のような状況:
- ダウンキャストを使う。
- static_castでもまず問題はない。
- だが、人間は間違う。万が一の間違いは検出したい。
それで、開発中にはdynamic_castを使って、最終的にはstatic_castにすることに。開発中(デバッグモード)かリリースかは、C/C++標準のNDEBUG定数で判断することにする。
アイデアは、
#ifdef NDEBUG // リリース # define DOWN_CAST static_cast #else // 開発中 # define DOWN_CAST dynamic_cast #endif
だが、これでは素朴過ぎる。
- dynamic_castで失敗したら、それはトンデモナイ間違いなので例外を投げるべき。
- キャスト前に(最初から)nullptrなら、それは許す。リンクリスト(linked list)の終端などで、nullptrを使うことがあるので、nullptrを禁止できない。
- なので、最初からのnullptrとdynamic_cast失敗時のnullptrは別に扱う。
まず次のような関数テンプレートを定義しておく。
// SuperTは、スーパークラスのオブジェクトを指すポインタ型 // SubTは、サブクラスのオブジェクトを指すポインタ型 template<typename SuperT, typename SubT> SubT safe_down_cast(SuperT ptr) { if (ptr == nullptr) { return nullptr; } SubT new_ptr = dynamic_cast<SubT>(ptr); assert(new_ptr != nullptr); // ここで間違いを検出 return new_ptr; }
この関数は型パラメーターを2個取るが、実際に使う時はSuperTの方を具体化する。
class ActualSuperClass; template<typename SubT> SubT actual_safe_down_cast(ActualSuperClass* ptr) { return safe_down_cast<ActualSuperClass*, SubT>(ptr); }
actual_safe_down_castは型パラメータを1個、引数にポインタ式を1個取る。これはstatic_castと同じ構文だから、互いに置き換えることが出来る。
#ifdef NDEBUG // リリース # define ACTUAL_DOWN_CAST static_cast #else // 開発中 # define ACTUAL_DOWN_CAST actual_safe_down_cast #endif
コード中でダウンキャストが必要になったら、ACTUAL_DOWN_CAST
まだ実際にやってないが、たぶん動くだろう。