このブログは、旧・はてなダイアリー「檜山正幸のキマイラ飼育記 メモ編」(http://d.hatena.ne.jp/m-hiyama-memo/)のデータを移行・保存したものであり、今後(2019年1月以降)更新の予定はありません。

今後の更新は、新しいブログ http://m-hiyama-memo.hatenablog.com/ で行います。

ダウンキャスト再論

次のような状況:

  1. ダウンキャストを使う。
  2. static_castでもまず問題はない。
  3. だが、人間は間違う。万が一の間違いは検出したい。

それで、開発中には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(ptr) とする。NDEBUGが定義されれば、これは static_cast(ptr) に置換される。

まだ実際にやってないが、たぶん動くだろう。