C++/RTTI
RTTIの概要
編集Run-Time Type Infomation(RTTI)はC++が提供する機能の1つで、実行時にオブジェクトの型情報を取得することができます。これにより、プログラムの実行中にオブジェクトの型を動的に検査し、適切な処理を行うことが可能になります。RTTIは以下のようなシーンで役立ちます。
- 継承ヒエラルキーにおけるダウンキャスト
- 汎用的なコンテナやアルゴリズムの実装
- プラグインシステムやリフレクションライブラリの構築
- デバッグ時のオブジェクト型の特定
typeid演算子
編集#include <iostream> #include <typeinfo> class Base { /* ... */ }; class Derived : public Base { /* ... */ }; auto main() -> int { Base* b = new Derived(); std::cout << typeid(*b).name() << std::endl; // 出力: "4Base" return 0; }
typeid演算子を使うことで、オブジェクトやデータ型の型情報オブジェクトを取得できます。型情報オブジェクトはstd::type_infoクラスのインスタンスで、name()メンバ関数を呼び出すと型名を表す文字列を取得できます。上の例では"4Base"と表示されますが、型名の表示形式は実装依存です。
type_infoクラス
編集type_infoクラスは、typeinfo ヘッダーで定義されている型情報を表すクラスです。主なメンバ関数は以下の通りです。
name()
- 型名を表す文字列を返します。ただし、文字列の形式は実装依存です。
before(const type_info&)
- 引数の型情報オブジェクトが、呼び出し元のオブジェクトよりも同一の型、または派生した型である場合にtrueを返します。
operator==
,operator!=
- 型情報オブジェクトが等しいかどうかを判定します。
Derived d; Base b; if (typeid(d) == typeid(Derived)) { std::cout << "d is a Derived object" << std::endl; } if (typeid(b).before(typeid(Derived))) { std::cout << "b is a Base or Derived object" << std::endl; }
typeinfo ヘッダー
編集typeinfo ヘッダーには、type_infoクラスとbad_typeid例外クラスが定義されています。
type_info
- 型情報を表すクラス
bad_typeid
- typeidで無効な型情報が発生した場合にスローされる例外
#include <iostream> #include <typeinfo> class Base { virtual void f() {} }; class Derived : Base {}; auto main() -> int { try { Base b; auto const &d = dynamic_cast<Derived &>(b); // ! } catch (std::bad_cast &e) { std::cerr << e.what() << std::endl; // @ } }
RTTIの有効化
編集RTTIを利用するには、コンパイル時にRTTIを有効にする必要があります。Visual C++では/GRオプションを付けてコンパイルします。
g++ -frtti -Wall main.cpp # GCC clang++ -frtti -Weverything main.cpp # Clang cl /GR /EHsc main.cpp # MSVC
RTTIを無効にすると、typeidやdynamic_castが使えなくなります。機能の有無によってコードの振る舞いが変わるため、注意が必要です。
dynamic_cast演算子
編集dynamic_castは安全なダウンキャストを行うための演算子です。ポインタ型やリファレンス型の変数に対して使用できます。
Base* b = new Derived(); Derived* d = dynamic_cast<Derived*>(b); // 成功 d = bが指すDerivedオブジェクト if (d) { // dを使った処理 } else { // キャスト失敗への対処 }
dynamic_castは実行時にオブジェクトの型チェックを行い、キャストが安全な場合はキャストされたポインタ/リファレンスを返します。キャストが不可能な場合は、ポインタ型ならnullptrが、リファレンス型なら例外std::bad_castがスローされます。
RTTIの制限事項
編集RTTIには以下のような制限事項があります。
- 配列型や関数型の型情報は取得できません
- 型エイリアスやtypedefで定義した名前は、本来の型名ではなくエイリアス名が返されます
- 無名の型や閉じられた列挙型の型情報は取得できません
using Alias = Derived; Alias a; std::cout << typeid(a).name() << std::endl; // 出力: Alias (Derivedではない)
RTTIの利用例
編集RTTIは様々な場面で利用できます。
- 型に基づく処理の分岐
auto process(const Base& b) -> void { if (typeid(b) == typeid(Derived)) { // Derivedクラスに対する特別な処理 } else { // 基底クラスに対する標準的な処理 } }
- 型情報の出力
std::cout << "Object type: " << typeid(obj).name() << std::endl;
- ダウンキャスト
Base* b = getObject(); // 実行時に取得したオブジェクト if (Derived* d = dynamic_cast<Derived*>(b)) { // dの処理 } else { // キャスト失敗への対処 }
RTTIとその他の型情報取得手段の比較
編集RTTIの他にも、C++には型情報を取得する手段がいくつかあります。
- 静的キャスト
- static_castは、プログラム実行時ではなく、コンパイル時に型チェックを行うキャストです。安全性が高い反面、RTTIほど柔軟性はありません。
Base* b = new Derived(); Derived* d = static_cast<Derived*>(b); // コンパイル時に型チェック
- 型特性
- C++11から導入された型特性(type traits)は、コンパイル時にデータ型の性質を調べる機能です。RTTIよりも高速ですが、実行時の型情報は取得できません。
if (std::is_base_of<Base, Derived>::value) { // DerivedがBaseを継承していることを保証 }
RTTIはオーバーヘッドが高い分、実行時に柔軟な型チェックが可能です。一方、コンパイル時の手段は速度が速いものの、機能に制限があります。用途に合わせて適切な手段を選ぶ必要があります。
まとめ
編集RTTIは実行時にオブジェクトの型情報を取得する機能です。typeinfo型やtypeid演算子、dynamic_castなどを使って、安全なダウンキャストやオブジェクトの型に応じた処理の分岐を行えます。一方で、型情報の取得範囲に制限があり、また実行時のオーバーヘッドが生じるというデメリットもあります。
他の言語と比べると、C++のRTTIは機能が制限されています。Javaなどの言語では、クラスのメンバやメソッドにもアクセスできるリフレクション機能が標準で提供されています。一方で、C++ではコンパイラ最適化のためにメタデータが捨てられ、実行時の型情報が制限されているためです。
将来的にC++にリフレクション機能が導入される可能性もありますが、現時点ではRTTIとその他の型情報取得手段を適切に組み合わせて、安全で効率的なプログラミングを行う必要があります。