コンテンツにスキップ

C++/RTTI

出典: フリー教科書『ウィキブックス(Wikibooks)』
< C++

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とその他の型情報取得手段を適切に組み合わせて、安全で効率的なプログラミングを行う必要があります。