【C++】static_castとreinterpret_cast、またdynamic_castの違い

2022-10-05

C++のキャストについてあまりよく知らなかったので調べた。

先にまとめ:

  • reinterpret_castは無条件のキャスト(C言語のキャストと同じで内容保証なし)
  • static_castはダウンキャスト用で、使える場合にはこちらを使うべき
  • 動的に(実行時に)型チェックするにはdynamic_castを使う(仮想関数とRTTI必須)

static_castとreinterpret_cast

まずstatic_castreinterpret_castはどちらも型を強制的に変換するものという認識で、違いがよく分かってなかった。

reinterpret_cast無条件にポインタを変換するのに対し、 static_cast変換元と先のクラスが親と子の関係でなければコンパイルエラーが発生する、という違いがある:

class Foo {};
class Bar {};
Foo* foo = new Foo();
printf("foo=%p\n", foo);
// Bar* bar = static_cast<Bar*>(foo); // static_castではエラーが出る: static_cast from 'Foo *' to 'Bar *', which are not related by inheritance, is not allowed
Bar* bar = reinterpret_cast<Bar*>(foo); // こちらはコンパイルが通る
printf("bar=%p\n", bar);

ということで static_cast は用途が制限されている、という違いがある。 言うなれば reinterpret_cast がCのキャストと同じもの。

  • void* からの変換にはどちらも使える
  • コンパイル時に判定するだけなので、実行時には判定はない
  • どちらも変換後の内容が実際に変換先クラスのインスタンスであるかどうかは保証されない

多重継承している場合

Base1Base2 を継承した Derived クラスがあったとする。 Derived から Base2 にアップキャストした場合、第1の親クラスじゃないのでポインタが調整される:

class Base1 { protected: virtual ~Base1(){} int x; };
class Base2 { protected: virtual ~Base2(){} int y; };
class Derived : public Base1, public Base2 { protected: virtual ~Derived(){}};

Derived* d = new Derived();
printf("d =%p\n", &d); // d =0x14d605dc0

Base2* b2 = d; // アップキャスト
printf("b2=%p\n", b2); // b2=0x14d605dd0 <== Base1のメンバ変数分、調整される

その状態から Derived* への static_cast を使用することでオフセットを考慮してダウンキャストされ、元のポインタが復元できる:

Derived* d2 = static_cast<Derived*>(b2);
printf("d2=%p\n", d2); // d2=0x14d605dc0 <== 復元できる

reinterpret_cast では考慮されないので復元はできない(オフセットがずれたまま)。 コンパイルワーニングで警告はしてくれる:

Derived* d3 = reinterpret_cast<Derived*>(b2);
// warning: 'reinterpret_cast' to class 'Derived *' from its base at non-zero offset 'Base2 *' behaves differently from 'static_cast' [-Wreinterpret-base-class]
// note: use 'static_cast' to adjust the pointer correctly while downcasting

printf("d3=%p\n", d3); // d3=0x14d605dd0 <== 復元されない

dynamic_cast

dynamic_castもダウンキャスト(親クラスから子クラスへの変換)に使用する (親子関係じゃない場合はコンパイルエラー)。 実行時に実際に意図したクラス(またはそれを継承したクラス)かどうかを判定して そうであれば変換し、違ったらnullptrを返す。 実行時型情報(RTTI)を用いるので、仮想関数を持っている必要がある。

多重継承している場合

dynamic_castBase2*からDerived* が復元できる。 static_castとの違いは、Derived*由来じゃないBase2*の場合にはnullptrが返る:

Base2* other_b2 = new Base2();
printf("other_b2=%p\n", other_b2); // other_b2=0x11de05bb0
Derived* dyncasted = dynamic_cast<Derived*>(other_b2);
printf("dyncasted=%p\n", dyncasted); // dyncasted=0x0 <== Derived由来じゃないことが判定できている
Derived* staticcasted = static_cast<Derived*>(other_b2);
printf("staticcasted=%p\n", staticcasted); // staticcasted=0x11de05ba0 <== Derived由来じゃないが実行時に判定されないため、無効なポインタが返る

const_cast

const_cast はコンパイル時に const ポインタをconstなしポインタとして解釈するよう指示するだけなので、特に不明点はない。