C++言語解説:2-5.RTTIとキャスト
2002-07-30 |
[C++ Index Top] [Prev] [Next] |
[概要] ポリモーフィズムの実装により重要となる、実行時型識別の機能と、C++で追加さ れたキャストについて学習します。 [構成]・RTTI(実行時型識別) * 実行時ポリモーフィズムとコンパイル時ポリモーフィズム * typeid()演算子 * ポインタの指すオブジェクトの型 * 汎用クラスの型識別 ・キャスト演算子 * dynamic_cast演算子 * const_cast演算子 * static_cast演算子 * reinterpret_cast演算子 ●RTTI(実行時型識別) ◆実行時ポリモーフィズムとコンパイル時ポリモーフィズム ・ここまで、ポリモーフィズム(多態性)を実現するために数種類の方法を学びました。 オーバーロード(関数/演算子) オーバーライド(仮想関数) テンプレート ・これらは、オブジェクトの型を決めるタイミングによって2種に分類されます。 実行時ポリモーフィズム : オーバーライド コンパイル時ポリモーフィズム : オーバーロード、テンプレート ・2種の使い分けとして、汎用性が高いのは実行時ポリモーフィズムですが、これに は「実行速度の低下」に関する懸念があります。従って、実行速度を重視する場合 はコンパイル時ポリモーフィズムを採用することになります。 ◆typeid()演算子 ・C++では、ポリモーフィズムのサポートにより、実行時の段階にならないとオブジェ クトの型を確定できない状況が発生します。これを実行時型識別-RTTI(run-time type identification)と呼びます。そのためC++ではオブジェクトの型を取得する演 算子 typeid() が用意されています。 ・typeid()演算子を使用するには<typeinfo>ヘッダが必要です。typeid()演算子は typeid(オブジェクト) のように使用し、引数(オペランド)に指定したオブジェクトの型情報を持つ type_infoオブジェクトへの参照を返します。尚、引数に指定できるのは、オブジェ クト以外に 演算式 (演算結果の型) 型名 (int, double, クラス名等の型名) もあります。 ・type_infoは<typeinfo>ヘッダ内で定義されているクラスです。処理系に依存しない 内容として以下のメンバ関数を持ちます。 bool operator==(const type_info&); bool operator!=(const type_info&); bool before(const type_info&); const char* name(); ・演算子関数「==」と「!=」は、オブジェクト同士の型比較機能です。結果はbool型 なので true/false となります(*1)。 ・before()関数は、オブジェクト型情報の照合順位において、呼び出し側のtypeinfo オブジェクトと、仮引数のtypeinfoオブジェクトを比較し、呼び出し側が位置とし て「前」にあればtrueを返します。 ・照合順位は、継承や階層の順番とは全く関係無く、型の種類で決まっています。こ れはbefore()関数により、型の種類によってソートが可能であることを意味してい ます。しかし使用する機会はあまりないでしょう。 ・name()関数は型名そのものへのchar*型ポインタを返します。型名を保有するメモリ 領域はシステム側で確保されているので、delete[]で解放してはいけません。 ・List1にtypeid()の使用例を示します。 [List1.typeid()の使用例] ┌───────────────────────────────────┐ #include <iostream> #include <typeinfo> using namespace std; class Counter { private: int mi_count; public: void Init(){ mi_count=0; return; } void CountUp(){ mi_count++; return; } void CountDown(){ mi_count--; return; } int GetCount(){ return mi_count; } }; int main() { int i_a; //int型オブジェクト double d_b; //double型オブジェクト Counter counter_c; //Counter型オブジェクト Counter counter_d; //Counter型オブジェクト cout << "i_aの型:" << typeid(i_a).name() << endl; cout << "d_bの型:" << typeid(d_b).name() << endl; cout << "counter_cの型:" << typeid(counter_c).name() << endl; cout << "counter_dの型:" << typeid(counter_d).name() << endl; if (typeid(counter_c)==typeid(counter_d)) { cout << "counter_cとcounter_dは同じ型" << endl; } if (typeid(i_a)!=typeid(d_b)) { cout << "i_aとd_bは異なる型" << endl; } if (typeid(i_a)==typeid(int)) { cout << "i_aはint型" << endl; } return 0; } └───────────────────────────────────┘ ┌───────────────────────────────────┐ [出力結果] i_aの型:int d_bの型:double counter_cの型:Counter counter_dの型:Counter counter_cとcounter_dは同じ型 i_aとd_bは異なる型 i_aはint型 └───────────────────────────────────┘ ◆ポインタの指すオブジェクトの型 ・C++でポリモーフィズムの機能を使用するには、オブジェクトをポインタ又は参照で 指し示します。 ・typeid()演算子をポリモーフィックな 基本クラスポインタ 基本クラスオブジェクト に適用すると、実際に指しているオブジェクトの型情報を得ることができます。使 用例をList2に示します。 [List2.ポリモーフィックオブジェクトへのtypeid()使用例] ┌───────────────────────────────────┐ #include <iostream> #include <typeinfo> using namespace std; class Animal { //抽象クラス public: virtual void Voice() = 0; }; class Dog : public Animal { public: void Voice() { cout << "ワンワン" << endl; return; } }; class Cat : public Animal { public: void Voice() { cout << "ニャーニャー" << endl; return; } }; void SubFunc(Animal& animal); int main() { Animal* p_animal; int i_select; cout << "犬(1)が好き? 猫(2)が好き? : "; cin >> i_select; switch(i_select) { case 1: p_animal = new Dog; break; default: p_animal = new Cat; break; } cout << "基本クラスポインタ: "; cout << typeid(*p_animal).name() << " : "; p_animal->Voice(); SubFunc(*p_animal); if (typeid(*p_animal) == typeid(Dog)) { cout << "やっぱり犬ですよね" << endl; } else { cout << "やっぱり猫ですよね" << endl; } delete p_animal; return 0; } void SubFunc(Animal& animal) { cout << "参照: "; cout << typeid(animal).name() << " : "; animal.Voice(); return; } └───────────────────────────────────┘ ┌───────────────────────────────────┐ [出力結果] C:> prog 犬(1)が好き? 猫(2)が好き? : 1 基本クラスポインタ: Dog : ワンワン 参照: Dog : ワンワン やっぱり犬ですよね C:> prog 犬(1)が好き? 猫(2)が好き? : 2 基本クラスポインタ: Cat : ニャーニャー 参照: Cat : ニャーニャー やっぱり猫ですよね └───────────────────────────────────┘ ・ポインタを介して型式別を行う場合、ポインタが「何のオブジェクトも指してい ない」つまり「nullポインタ」のケースも有り得ます。このとき、typeid()演算子 は「bad_typeid例外」を投入します。 ・本テキストでは、まだ「例外」を扱っていません。次回のテキストで説明する予定 ですが、List3に例を示しておきます。 [List3.typeid()の例外処理] ┌───────────────────────────────────┐ // // Animal, Dog, CatクラスはList2と同様 // int main() { Animal* p_animal; int i_select; cout << "犬(1)が好き? 猫(2)が好き? : "; cin >> i_select; switch(i_select) { case 1: p_animal = new Dog; break; case 2: p_animal = new Cat; break; default: p_animal = 0; break; //非選択時はnull } try { cout << typeid(*p_animal).name() << " : "; } catch(bad_typeid bt){ cout << "犬も猫も嫌いですか..." << endl; return 1; } p_animal->Voice(); delete p_animal; return 0; } └───────────────────────────────────┘ ┌───────────────────────────────────┐ [出力結果] C:> prog 犬(1)が好き? 猫(2)が好き? : 0 犬も猫も嫌いですか... C:>prog 犬(1)が好き? 猫(2)が好き? : 1 Dog : ワンワン └───────────────────────────────────┘ ◆汎用クラスの型識別 ・typeid()演算子は汎用クラスにも適用できます。List4に例を示します。 [List4.汎用クラスへのtypeid()の適用] ┌───────────────────────────────────┐ #include <iostream> #include <typeinfo> using namespace std; template <class T> class Figure { protected: T mt_base; T mt_height; public: Figure(T t_base, T t_height) { mt_base = t_base; mt_height = t_height; } virtual double Space() = 0; }; template <class T> class Triangle : public Figure<T> { public: Triangle(T t_base, T t_height) : Figure<T>(t_base, t_height){} double Space() { return (mt_base * mt_height * 0.5); } }; template <class T> class Square : public Figure<T> { public: Square(T t_base, T t_height) : Figure<T>(t_base, t_height){} double Space() { return (mt_base * mt_height); } }; int main() { Figure<double>* p_figure; int i_select1; int i_base, i_height; double d_base, d_height; cout << "三角形(1)? 四角形(2)? : "; cin >> i_select1; cout << "底辺=" ; cin >> d_base; cout << "高さ=" ; cin >> d_height; if (i_select1 == 1) { p_figure = new Triangle<double>(d_base, d_height); } else { p_figure = new Square<double>(d_base, d_height); } cout << "型=" << typeid(*p_figure).name() << endl; if (typeid(*p_figure)==typeid(Triangle<double>)) { cout << "三角形が選択されました" << endl; } else { cout << "四角形が選択されました" << endl; } cout << "面積=" << p_figure->Space() << endl; delete p_figure; return 0; } └───────────────────────────────────┘ ┌───────────────────────────────────┐ [出力結果] C:>prog 三角形(1)? 四角形(2)? : 1 底辺=10.3 高さ=5.2 型=Triangle<double> 三角形が選択されました 面積=26.78 C:>prog 三角形(1)? 四角形(2)? : 2 底辺=10.3 高さ=5.2 型=Square<double> 四角形が選択されました 面積=53.56 └───────────────────────────────────┘ ・汎用クラスそのものは、コンパイル時に型が決まります。ただし、List4のように仮 想関数との併用で実行時ポリモーフィズムが適用される場合を想定し、typeid()演 算子も汎用クラスに対応しています。 ・ここまでtypeid()演算子による実行時型識別の方法を見てきましたが、実行時型識 別の実行速度は早いと言い難く、型に基づく実行内容の分類が必要な場合はできる だけ仮想関数を使用すべきです。 ●キャスト演算子 ◆dynamic_cast演算子 ・(1-2章.基本データ型)でキャストを説明していますが、このキャストを使うと何で もできます。例え意味的に矛盾していることが明白でも実行できます。 ・C++ではポリモーフィズムのサポートにより、型変換の機会も増えてきています。し かしながら従来のキャストだけでは、間違った操作をしてしまうケースもあるため 「より安全なキャスト」を行うために演算子が追加されました。 dynamic_cast演算子 const_cast演算子 static_cast演算子 reinterpret_cast演算子 ・はじめに、dynamic_cast演算子について説明します。ポリモーフィックな基本クラ スポインタが、事実上派生クラスのオブジェクトを指している場合、派生クラスポ インタへキャストしても問題はありません。 ・しかしそれ以外のケース、例えば基本クラスオブジェクトを指しているポインタが 派生クラスポインタへキャストされるのは望ましくありません。 ・このような場合、dynamic_cast演算子を利用すれば、ポリモーフィズムとして正当 なキャストのみを実行することができます。 ・尚、dynamic_castが失敗した場合、対象キャストがポインタであればnullポインタ を返し、参照であればbad_cast例外が投入されます。例外処理については次回説明 する予定です。それではList5に例を示します。 [List5.dynamic_cast演算子の使用例] ┌───────────────────────────────────┐ #include <iostream> using namespace std; class Base { public: virtual void Exe() { cout << "Base!!" << endl; } }; class Derived : public Base { public: void Exe() { cout << "Derived!!" << endl; } }; class Other { public: virtual void Exe() { cout << "Other Base!!" << endl; } }; int main() { Base* p_base; Base base; Derived* p_derived; Derived derived; Other other; p_base = &base; p_derived = dynamic_cast<Derived*>(p_base); cout << "case 1:"; if (p_derived != 0) p_derived->Exe(); else cout << "Fail" << endl; p_base = &derived; p_derived = dynamic_cast<Derived*>(p_base); cout << "case 2:"; if (p_derived != 0) p_derived->Exe(); else cout << "Fail" << endl; p_derived = &derived; p_base = dynamic_cast<Base*>(p_derived); cout << "case 3:"; if (p_derived != 0) p_base->Exe(); else cout << "Fail" << endl; try { cout << "case 4:"; Base& base2 = dynamic_cast<Base&>(derived); base2.Exe(); } catch(bad_cast bc){ cout << "Fail" << endl; } try { cout << "case 5:"; Base& base3 = dynamic_cast<Base&>(other); base3.Exe(); } catch(bad_cast bc){ cout << "Fail" << endl; } return 0; } └───────────────────────────────────┘ ┌───────────────────────────────────┐ [出力結果] case 1:Fail case 2:Derived!! case 3:Derived!! case 4:Derived!! case 5:Fail └───────────────────────────────────┘ ◆const_cast演算子 ・const_cast演算子は、const又はvolatile属性を変更するキャストとして使用しま す。当然ながらconst/volatile属性を除き、ターゲットオブジェクトの型は等しく なければなりません。 [List6.const_cast演算子の使用例] ┌───────────────────────────────────┐ #include <iostream> using namespace std; void SubFunc(const char* cp); int main() { char* cp_str = "abcd"; cout << "convert前 : " << cp_str << endl; SubFunc(cp_str); cout << "convert後 : " << cp_str << endl; return 0; } void SubFunc(const char* cp) { int i=0; char* cp_local = const_cast<char*>(cp); while(*(cp_local+i)){ if (*(cp_local+i)=='a') *(cp_local+i)='A'; i++; } return; } └───────────────────────────────────┘ ┌───────────────────────────────────┐ [出力結果] convert前 : abcd convert後 : Abcd └───────────────────────────────────┘ ・const char*のポインタをconst_castにより、通常のchar*へキャストしました。こ の結果、ポインタの指すオブジェクトに変更処理が加えられています。 ・通常const属性は「変えられたくない」から付けています。const_cast演算子は、そ れを「ねじ曲げる」という意味で非常に危険なものです。この機能を使用するには 注意が必要です。 ◆static_cast演算子 ・static_cast演算子は、関連する型の間の変換を行います。「関連する型」とは暗黙 の型変換が存在することを意味しています。よってstatic_castでは数値型をポイン タへ変換するような乱暴な真似はできません。 ・つまり、static_castを使用すれば、少なくともキャストの危険性が「暗黙の型変換」 で考慮する内容に限定されていると解釈することができます。 [List7.static_cast演算子の使用例(1):コンパイル可能] ┌───────────────────────────────────┐ #include <iostream> using namespace std; int main() { int i_a; double d_b = 10.5; i_a = static_cast<int>(d_b); cout << i_a << endl; return 0; } └───────────────────────────────────┘ ┌───────────────────────────────────┐ [出力結果] 10 └───────────────────────────────────┘ [List8.static_cast演算子の使用例(2):コンパイル不可] ┌───────────────────────────────────┐ #include <iostream> using namespace std; int main() { int* ip_a; int i_a = 10; ip_a = static_cast<int*>(i_a); cout << ip_a << endl; return 0; } └───────────────────────────────────┘ ◆reinterpret_cast演算子 ・reinterpret_cast演算子は、単純に型を変換します。例えばポインタのアドレスデ ータを数値として扱いたい場合などに使用します。 [その他のポインタ]←→[int*ポインタ]←→[int] ・「移植性0」のコードになる可能性が高く、その危険度も最高です。この演算子を使 用する場合は、その妥当性に細心の注意を払うようにして下さい。 [List9.reinterpret_cast演算子の使用例] ┌───────────────────────────────────┐ #include <iostream> using namespace std; int main() { int* ip_a; int i_a = 10; ip_a = reinterpret_cast<int*>(i_a); cout << ip_a << endl; return 0; } └───────────────────────────────────┘ ┌───────────────────────────────────┐ [出力結果] 0000000A └───────────────────────────────────┘ (*1)同型のオブジェクトを表現するtype_infoオブジェクトが同一とは限りません。参照オ ブジェクトのポインタを取得して、同一アドレスか否かを比較しても望む結果は得ら れません。 [Revision Table] |Revision |Date |Comments |----------|-----------|----------------------------------------------------- |1.00 |2002-07-30 |初版 |1.01 |2002-08-01 |リンク追加 [end] |
Copyright(C) 2002 Altmo
|
[C++ Index Top] [Prev] [Next] |