この章では、C++ 標準の新しいキャスト演算子、すなわち const_cast、reinterpret_cast、static_cast、dynamic_cast について説明します。キャストとは、オブジェクトや値の型を、別の型に変換することです。
これらのキャスト演算子を使用すると、従来のキャスト演算子よりも緻密な制御を行うことができます。たとえば、dynamic_cast<> 演算子では、多相クラスのポインタの実際の型を確認することができます。新形式のキャストには、_cast を検索することで、テキストエディタで簡単に検出できるという利点もあります。従来のキャストは、構文チェックを行わないと検出できません。
新しいキャストは、それぞれ従来のキャスト表記で行うことのできる各種のキャスト操作の一部だけを実行します。たとえば、const_cast<int*>(v) は、従来であれば (int*)v と記述することができます。新しいキャストは、コードの意図をより明確に表現し、コンパイラがより的確なチェックを行えるように、実行可能な各種のキャスト操作を単に類別したものです。
キャスト演算子は常に有効になります。これらを無効にすることはできません。
式 const_cast<T >(v) を使用して、ポインタまたは参照の const 修飾子または volatile 修飾子を変更することができます。新しい形式のキャストのうち、const 修飾子を削除できるのは const_cast<> のみです。T はポインタ、参照、またはメンバー型へのポインタである必要があります。
class A { public: virtual void f(); int i; }; extern const volatile int* cvip; extern int* ip; void use_of_const_cast() { const A a1; const_cast<A&>(a1).f(); // remove const ip = const_cast<int*> (cvip); // remove const and volatile } |
式 reinterpret_cast<T >(v) は式 v の値の解釈を変更します。この式は、ポインタ型と整数型の間、2 つの無関係なポインタ型の間、ポインタ型からメンバー型へ、ポインタ型から関数型へ、という各種の変換に使用できます。
reinterpret_cast 演算子を使用すると、未定義の結果または実装に依存しない結果を出すことがあります。次に、確実な動作について説明します。
データオブジェクトまたは関数へのポインタ (メンバーへのポインタは除く) は、それを十分保持できる大きさの任意の整数型に変換できます。long 型は十分大きいため、C++ コンパイラがサポートするアーキテクチャーでは常にポインタ値を保持できます。元の型に戻しても、値は元の値と同じになります。
(非メンバー) 関数へのポインタは、別の (非メンバー) 関数型へのポインタに変換できます。元の型に戻しても、値は元の値と同じになります。
新しい型が元の型よりも厳しい整列条件を持たない場合、オブジェクトへのポインタは別のオブジェクト型へのポインタに変換できます。元の型に戻しても、値は元の値と同じになります。
reinterpret_cast 演算子を使用して型「T1 のポインタ」の式を型「T2 のポインタ」に変換できる場合、型 T1 の左辺値は型「T2 の参照」に変換できます。
T1 と T2 の両方が関数型であるか両方がオブジェクト型である場合、「型 T1 の Xのメンバーを指すポインタ」型の右辺値は、「型 T2 の Y のメンバーを指すポインタ」型の右辺値に明示的に変換できます。
変換が許可されている場合は、ある型のヌルポインタは別の型のヌルポインタに変換されたあともヌルポインタのままです。
reinterpret_cast 演算子を使用して、const を const でない型にキャストすることはできません。このようにキャストするには const_cast を使用します。
reinterpret_cast 演算子を使用して、ポインタと、同じクラス階層に存在する別のクラスの間の変換を行うことはできません。このように変換するには、静的キャストまたは動的キャストを使用します。reinterpret_cast は、必要があっても調整は行いません。これを次の例で説明します。
class A {int a; public: A();}; class B: public A {int b, c;}; void use_of_reinterpret_cast() { A a1; long l = reinterpret_cast<long>(&a1); A* ap = reinterpret_cast<A*>(l); // safe B* bp = reinterpret_cast<B*>(&a1); // unsafe const A a2; ap = reinterpret_cast<A*>(&a2); // error, const removed } |
式 static_cast<T >(v) は、式の値 v を型 T の値に変換します。この式は、暗黙的に実行されるすべての型変換に使用できます。さらに、いかなる値でも void にキャストすることができ、いかなる暗黙的型変換でも、そのキャストが旧式のキャストと同様に正当であるかぎり、反転させることができます。
class B {...}; class C: public B {...}; enum E {first=1, second=2, third=3}; void use_of_static_cast(C* c1) { B* bp = c1; // implicit conversion C* c2 = static_cast<C*>(bp); // reverse implicit conversion int i = second; // implicit conversion E e = static_cast<E>(i); // reverse implicit conversion } |
static_cast 演算子を使用して、const を const 以外の型にするようなキャストを行うことはできません。階層の下位に (基底から派生ポインタまたは参照へ) キャストするには static_cast を使用できますが、変換は検証されず、結果は使用できない場合があります。抽象基底クラスから下位へのキャストには、static_cast は使用できません。
クラスへのポインタまたは参照は、そのクラスから派生されたすべてのクラスを実際に指す (参照する) ことができます。場合によっては、オブジェクトの完全派生クラス、またはその完全なオブジェクトのほかのサブオブジェクトへのポインタを得る方が望ましいことがあります。動的キャストによってこれが可能になります。
互換モード (-compat[=4]) でコンパイルする場合、プログラムが動的キャストを使用している場合は、-features=rtti を付けてコンパイルする必要があります。
動的な型のキャストは、あるクラス T1 へのポインタまたは参照を、別のクラス T2 のポインタまたは参照に変換します。T1 とT2 は、同じ階層内にある必要があります。両クラスとも (公開派生を介して) アクセス可能でなければならず、変換はあいまいであってはいけません。また、変換が派生クラスからその基底クラスの 1 つに対するものでないかぎり、T1 と T2 の両方が入った階層の最小の部分は多相性がある必要があります (少なくとも仮想関数が 1 つ存在すること)。
式 dynamic_cast<T>(v) では、v はキャストされる式であり、T はキャストの対象となる型です。T は完全なクラス型 (定義が参照できるもの) へのポインタまたは参照であるか、あるいは「cv void へのポインタ」である必要があります。ここで cv は空の文字列、const、volatile、const volatile のいずれかです。
階層の上位にキャストする場合で、v が指す (参照する) 型の基底クラスを T が指す(あるいは参照する) 場合、変換は static_cast<T>(v) で行われるものと同じです。
T が void* の場合、結果はオブジェクト全体のポインタになります。つまり、v はあるオブジェクト全体の基底クラスの 1 つを指す可能性があります。この場合、dynamic_cast<void*>(v) の結果は、v をオブジェクト全体の型 (種類は問わない) に変換したあとで void* に変換した場合と同じです。
void* にキャストする場合、階層に多相性がある必要があります (仮想関数が存在すること)。結果は実行時に検証されます。
階層の下位または全体にキャストする場合、階層に多相性がある必要があります (仮想関数を持つ必要がある)。結果は実行時に検証されます。
階層の下位または全体にキャストする場合、v から T に変換できないことがあります。たとえば、試行された変換があいまいであったり、T に対するアクセスが不可能であったり、あるいは必要な型のオブジェクトを v が指さない (あるいは参照しない) 場合がこれに当たります。実行時検査に失敗し、T がポインタ型である場合、キャスト式の値は型 T のヌルポインタです。T が参照型の場合、何も返されず (C++ にはヌル参照は存在しない)、標準例外 std::bad_cast が送出されます。
たとえば、次の公開派生のコード例は正常に実行されます。
#include <assert.h> #include <stddef.h> // for NULL class A {public: virtual void f();}; class B {public: virtual void g();}; class AB: public virtual A, public B {}; void simple_dynamic_casts() { AB ab; B* bp = &ab; // no casts needed A* ap = &ab; AB& abr = dynamic_cast<AB&>(*bp); // succeeds ap = dynamic_cast<A*>(bp); assert(ap!= NULL); bp = dynamic_cast<B*>(ap); assert(bp!= NULL); ap = dynamic_cast<A*>(&abr); assert(ap!= NULL); bp = dynamic_cast<B*>(&abr); assert(bp!= NULL); } |
これに対して、次のコード例は正しく実行されません。基底クラス B にアクセスできないからです。
#include <assert.h> #include <stddef.h> // for NULL #include <typeinfo> class A {public: virtual void f() {}}; class B {public: virtual void g() {}}; class AB: public virtual A, private B {}; void attempted_casts() { AB ab; B* bp = (B*)&ab; // C-style cast needed to break protection A* ap = dynamic_cast<A*>(bp); // fails, B is inaccessible assert(ap == NULL); try { AB& abr = dynamic_cast<AB&>(*bp); // fails, B is inaccessible } catch(const std::bad_cast&) { return; // failed reference cast caught here } assert(0); // should not get here } |
1 つの基底クラスについて仮想継承と多重継承が存在する場合には、実際の動的キャストは一意の照合を識別することができる必要があります。もし照合が一意でないならば、そのキャストは失敗します。たとえば、次の追加クラス定義が与えられたとします。
class AB_B: public AB, public B {}; class AB_B__AB: public AB_B, public AB {}; |
次に例を示します。
void complex_dynamic_casts() { AB_B__AB ab_b__ab; A*ap = &ab_b__ab; // okay: finds unique A statically AB*abp = dynamic_cast<AB*>(ap); // fails: ambiguous assert(abp == NULL); // STATIC ERROR: AB_B* ab_bp = (AB_B*)ap; // not a dynamic cast AB_B*ab_bp = dynamic_cast<AB_B*>(ap); // dynamic one is okay assert(ab_bp!= NULL); } |
dynamic_cast のエラー時のヌル (NULL) ポインタの戻り値は、コード中の 2 つのブロック (1 つは型推定が正しい場合にキャストを処理するためのもの、もう 1 つは正しくない場合のもの) の間の条件として役立ちます。
void using_dynamic_cast(A* ap) { if (AB *abp = dynamic_cast<AB*>(ap)) { // abp is non-null, // so ap was a pointer to an AB object // go ahead and use abp process_AB(abp);} else { // abp is null, // so ap was NOT a pointer to an AB object // do not use abp process_not_AB(ap); } } |
互換モード (-compat[=4]) では、-features=rtti コンパイラオプションによって実行時の型情報が有効になっていないと、コンパイラは dynamic_cast を static_cast に変換し、警告メッセージを出します。
実行時型情報が無効にされている場合、コンパイラは dynamic_cast<T&> を static_cast<T&> に変換し、警告を発行します。参照型への動的キャストを行う場合は、そのキャストが実行時に無効であると判明したときに送出される例外が必要です例外の詳細については、「7.5.3 問題がある検索の回避」を参照してください。
動的キャストは必然的に、仮想関数による変換のような適切な設計パターンより遅くなります。Erich Gamma 著 (ソフトバンク)『オブジェクト指向における再利用のためのデザインパターン』を参照してください。