コンテンツにスキップ

ダウンキャスト

基底クラスの参照やポインタから派生クラスの参照やポインタへの型変換をダウンキャストといいます。 C++ では、ダウンキャストをする際に dynamic_caststatic_cast を使います。

ダウンキャストをしたクラスを扱う場合、 キャスト失敗を考慮したコードを書く必要があったり、 メモリアクセス違反を引き起こすようなコードになる可能性があります。 そのため、ダウンキャストを行わないで済むようなコードを書くことが望ましいです。

dynamic_cast によるダウンキャスト

dynamic_cast が使えるのは仮想関数を持ったクラスに限定されます。

class Base {
 public:
    virtual ~Base(){}
};
class Sub1 : public Base {};

Sub1* sub1 = dynamic_cast<Sub1*>(new Base());  // ダウンキャスト

dynamic_cast は他のキャスト演算子と異なり、実行時にキャストの成否を判断します。 dynamic_cast実行時型情報 (RTTI) を確認した上で、継承関係が不正であった場合、キャストに失敗します。

「ポインタでのキャスト」と「参照でのキャスト」では、失敗時の挙動が異なります。

ポインタでのキャストでは、失敗時に nullptr を返却します。

class Base {
 public:
    virtual ~Base(){}
};
class Sub1 : public Base {};
class Sub2 : public Base {};

int main() {
    Base* base = new Sub1;
    Sub2* sub2 = dynamic_cast<Sub2*>(base);  // ダウンキャスト
    if (sub2 == nullptr) {
        std::cout << "ダウンキャスト失敗" << std::endl;
    }

    return 0;
}

参照でのキャストでは、失敗時に std::bad_cast 例外を送出します。

class Base {
 public:
    virtual ~Base(){}
};
class Sub1 : public Base {};
class Sub2 : public Base {};

int main() {
    try {
        Base* base = new Sub1();
        Sub2& sub2 = dynamic_cast<Sub2&>(*base);  // ダウンキャスト
    } catch (const std::bad_cast&) {
        std::cout << "ダウンキャスト失敗" << std::endl;
    }

    return 0;
}

static_cast によるダウンキャスト

キャスト元のポインタが正しくキャスト後のオブジェクトを指していることが自明であれば、 static_cast を利用してダウンキャストを行うことも可能です。

class Base {
 public:
    virtual ~Base(){}
};
class Sub1 : public Base {
 public:
    int x_;
};
class Sub2 : public Base {}

Base* base = new Sub1();  // Sub1 からのアップキャスト
Sub1* sub1 = static_cast<Sub1*>(base);  // Sub1 へのダウンキャスト
                                        // base の実体は Sub1 なので問題なし

static_castdynamic_cast とは違い、 実行時の型の情報をチェックしていないので、 次のような危険なダウンキャストも出来てしまいます。 static_cast の場合、キャストが成功しても動作は保証されないので注意が必要です。

class Base {
 public:
    virtual ~Base(){}
};
class Sub1 : public Base {
 public:
    int x_;
};
class Sub2 : public Base {}

Base* base = new Sub2();  // Sub2 からのアップキャスト
Sub1* sub1 = static_cast<Sub1*>(base);  // Sub1 へのダウンキャスト

sub1->x_ = 100;  // sub1 は Sub2 のメモリ領域を指したポインタ
                 // Sub2 には存在しない領域 x_ を参照しようとして不正なメモリアクセスになる