はじめに
C++の継承とポリモーフィズム(多態性)を使うと、親クラスへのポインタの配列に、様々な種類の子クラスオブジェクトをまとめて格納できます。しかし、時には「このポインタが今指しているのは、『戦士』オブジェクトなのか、それとも『魔法使い』オブジェクトなのか」を、プログラムの実行中に正確に知りたい場面があります。
このような、プログラム実行時にオブジェクトの型情報を取得するための仕組みが「実行時型情報 (Run-Time Type Information, RTTI)」です。C++では、<typeinfo>
ヘッダーをインクルードし、typeid
演算子を使うことで、このRTTIの機能を利用できます。
この記事では、typeid
を使って、親クラスへのポインタが指す先の、実際のオブジェクトの型を判別する方法を解説します。
typeid
を使った型判別のサンプルコード
このコードは、Character
(親クラス)へのポインタの配列に、Warrior
と Mage
という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
の長い連鎖が生まれるようなら、設計を見直す良い機会です。オブジェクト指向の原則を念頭に置き、適切な場面で活用しましょう。