C++の新しいキャスト
従来のキャストの問題点
異なる型への変換において、C/C++ではキャストが用いられます。
// intからlongへのキャスト int ival; int lval = (long)ival;
ご存知のとおり、キャストは非常に危険です。 本来ならば型の不一致によるコンパイルエラーをねじ伏せるのですから。
キャストの使われ方(意味)は、大きく3種(型変換/型変更/const外し)に分類されます。
- 型変換
// int から double へ int ival; double dval = (double)ival;
- 型変更
// long から int* へ long lval; int* iptr = (int*)lval;
- const外し
// const int* から int* へ const int* ciptr; int* iptr = (int*)ciptr;
上記のどの目的でキャストしても、構文としてはどれも同じ
(型)式
です。つまりコードからは”何が目的でキャストしたのかはっきりしない“のですね。
4種類の新しいキャスト
キャストは使わずに済ませられるならそれに越したとはないのですが、どうしても使わざるを得ないシチュエーションは少なからず存在します。
ならばせめて少しでも安全にキャストするために新たに定められたキャスト構文が、以下に挙げるstatic_cast / reinterpret_cast / const_cast / dynamic_cast です。
static_cast
static_cast<type>(expr)
static_castはexprの型からtypeへの暗黙の型変換、あるいはtypeからexprへの暗黙の型変換が存在する場合にだけキャストします。キャスト不可能であればコンパイルエラーとなります。
// int から long へ int ival; long lval = static_cast<long>(ival);
reinterpret_cast
reinterpret_cast<type>(expr)
reinterpret_castはtype(expr)が許されるなら、exprをtypeに単にキャストします。
// long から int* へ long lval; int* iptr = reinterpret_cast<int*>(lval);
reinterpret_castは単なる型変更であり、たとえ派生関係があったとしてもポインタのアドレス自体はキャスト前と変わりません。
その意味でreinterpret_castは非常に危険なキャストといえるでしょう。
const_cast
const_cast<type>(expr)
const_castはconstおよびvolatile修飾子を無効にするだけのキャストを行ないます。そのほかのときはコンパイルエラーとします。
// const int* から int* へ const int* ciptr; int* iptr = const_cast<int*>(ciptr);
dynamic_cast
dynamic_cast<type>(expr)
dynamic_castは基底クラスへのポインタ(or 参照)から派生クラスへのポインタ(or 参照)への型保証キャストを行ないます。
上記3種のキャストはコンパイル時にキャストしますが、dynamic_castは実行時に型の検査が行なわれ、変換不可能であれば0を返します。
class Base { ... };
class Derived : public Base { ... };
Base base;
Derived derived;
Derived* pd1 = dynamic_cast<Derived*>(&base); // 失敗 pd1 == 0
Derived* pd2 = dynamic_cast<Derived*>(&derived);
参照のキャストに失敗すると、std::bad_cast例外がthrowされます。
try {
Derived& rd1 = dynamic_cast<Derived&>(base);
} catch ( const std::bad_cast& e ) {
std::cout << e.what() << std::endl;
};
dynamic_castにより、従来のキャストでは不可能であった クロス・キャスト、そして抽象基底クラスからのダウン・キャストが可能になりました。
クロス・キャスト
/*
* Shape と Drawable は派生関係にない
*/
class Shape {
...
};
class Drawable {
public:
virtual void draw() =0;
};
/*
* Circle は Drawable にキャスト可能
*/
class Circle : public Shape, public Drawable {
public:
virtual void draw();
};
Shape* shape = new Circle;
/* 従来のキャスト (Drawable*)shape では、
* 場合によっては暴走する */
Drawable* drawable = dynamic_cast<Drawable*>(shape);
if ( drawable )
drawable->draw();
抽象基底クラスからのダウン・キャスト
class Machine {
...
};
class TV : virtual public Machine {
public:
void tune(int channel);
};
class VTR : virtual public Machine {
public:
void record();
};
class TeleVideo : public TV, public VTR {
...
};
Machine* machine = new TeleVideo;
/* 従来のキャスト (TeleVideo*)machine はコンパイルエラー */
TeleVideo* televideo = dynamic_cast<TeleVideo*>(machine);
if ( televideo ) {
televideo->tune(8);
televideo->record();
}
※注意
dynamic_castが適用できるのはポリモルフィック・クラス、 すなわち少なくとも一つのメンバ関数が仮想関数でなくてはなりません。
struct non_polymorpic { ... }; // 仮想関数が存在しない
struct something : non_polumorphic { ... };
non_polymorphic* p;
something* q = dynamic_cast<someting>(p); // error!
新しいキャストは従来のキャストよりはるかに安全です。
また、grepなどで”_cast”をキーにすればキャストした個所を簡単に検索できるという副次的な効果もあります。