C++言語解説:2-5.RTTIとキャスト
2002-07-30

[概要] ポリモーフィズムの実装により重要となる、実行時型識別の機能と、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