【C++】typeid の使い方 | 実行時のオブジェクトの型を判別する方法 (RTTI)

目次

はじめに

C++の継承とポリモーフィズム(多態性)を使うと、親クラスへのポインタの配列に、様々な種類の子クラスオブジェクトをまとめて格納できます。しかし、時には「このポインタが今指しているのは、『戦士』オブジェクトなのか、それとも『魔法使い』オブジェクトなのか」を、プログラムの実行中に正確に知りたい場面があります。

このような、プログラム実行時にオブジェクトの型情報を取得するための仕組みが「実行時型情報 (Run-Time Type Information, RTTI)」です。C++では、<typeinfo> ヘッダーをインクルードし、typeid 演算子を使うことで、このRTTIの機能を利用できます。

この記事では、typeid を使って、親クラスへのポインタが指す先の、実際のオブジェクトの型を判別する方法を解説します。


typeid を使った型判別のサンプルコード

このコードは、Character(親クラス)へのポインタの配列に、WarriorMage という2種類の子クラスオブジェクトを格納します。その後、ループの中で typeid を使って、各ポインタがどちらの型のオブジェクトを指しているかを判別します。

完成コード

#include <iostream>
#include <string>
#include <vector>
#include <typeinfo> // typeid を使うために必要

using namespace std;

// --- 親クラス(基底クラス) ---
// RTTIを有効にするには、基底クラスに少なくとも1つの仮想関数が必要
class Character {
public:
    string name;
    Character(string n) : name(n) {}
    virtual ~Character() {} // 仮想デストラクタ
};

// --- 子クラス(派生クラス) ---
class Warrior : public Character {
public:
    Warrior(string n) : Character(n) {}
};

class Mage : public Character {
public:
    Mage(string n) : Character(n) {}
};


int main() {
    // 親クラスへのポインタの配列に、異なる子クラスのオブジェクトを格納
    vector<Character*> party;
    party.push_back(new Warrior("戦士"));
    party.push_back(new Mage("魔法使い"));
    
    cout << "--- パーティメンバーの職業を判別 ---" << endl;

    for (Character* p_char : party) {
        // 1. typeidでポインタが指す先のオブジェクト(*p_char)の型を調べる
        if (typeid(*p_char) == typeid(Warrior)) {
            cout << p_char->name << " は、ウォリアーです。" << endl;
        }
        else if (typeid(*p_char) == typeid(Mage)) {
            cout << p_char->name << " は、メイジです。" << endl;
        }
        else {
            // .name()で、実際の型名を文字列として取得できる
            cout << p_char->name << " は、不明な職業です。(型: " << typeid(*p_char).name() << ")" << endl;
        }
    }

    // メモリの解放
    for (Character* p_char : party) {
        delete p_char;
    }
    
    return 0;
}

コードの解説

RTTIを有効にするための条件

typeid を使ってポリモーフィックな(多態的な)型判別を行うには、親クラスに少なくとも一つ以上の仮想関数が定義されている必要があります。

  • virtual ~Character() {}: デストラクタを仮想関数(仮想デストラクタ)にするのが、最も一般的で安全な方法です。これにより、delete p_char; のような親クラスのポインタ経由での delete が、正しく子クラスのデストラクタを呼び出すようにもなります。

<typeinfo> ヘッダーと typeid 演算子

  • #include <typeinfo>: typeid を利用するために、このヘッダーファイルをインクルードする必要があります。
  • typeid(*p_char): typeid 演算子は、引数として渡されたオブジェクトや式の「型情報(type_infoオブジェクト)」を返します。
    • ポインタ変数 p_char そのものではなく、*p_char のように間接参照して、ポインタが指し示している先のオブジェクトを渡すのがポイントです。
  • typeid(Warrior): typeid には、Warrior のような型名を直接渡すこともできます。
  • == での比較: typeid が返す type_info オブジェクト同士を == で比較することで、型が完全に一致するかどうかを判定できます。
  • .name(): type_info オブジェクトの .name() メンバ関数を呼び出すと、コンパイラ依存の形式ではありますが、その型の名前を文字列として取得できます。

まとめ

今回は、C++の実行時型情報(RTTI)と typeid 演算子を使って、オブジェクトの実際の型を判別する方法を解説しました。

  • 親クラスに仮想関数を定義することで、RTTIが有効になる。
  • <typeinfo> ヘッダーをインクルードし、typeid 演算子を使う。
  • typeid(*ポインタ) == typeid(クラス名) のように比較して、型を判別する。

typeid による型判別は便利ですが、多用すると、それは仮想関数を使ったポリモーフィズムで解決すべき問題かもしれません。if-else の長い連鎖が生まれるようなら、設計を見直す良い機会です。オブジェクト指向の原則を念頭に置き、適切な場面で活用しましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次