Menu
Coddy logo textTech

Dynamic Casting & RTTI

CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 62/104。

ポリモーフィズムを扱っている際、実行時にオブジェクトの実際の型を特定したり、基底クラスのポインタを派生クラスのポインタに安全に変換したりする必要がある場合があります。C++では、このような状況のためにRTTI (実行時型情報)dynamic_castが提供されています。

dynamic_cast は、継承階層内でのポインタや参照を安全に変換します。static_cast とは異なり、実行時チェックを行い、変換が無効な場合は nullptr を返します:

class Animal {
public:
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void bark() { std::cout << "Woof!" << std::endl; }
};

class Cat : public Animal {};

Animal* animal = new Dog();
Dog* dog = dynamic_cast<Dog*>(animal);  // 成功:有効なポインタを返します

if (dog) {
    dog->bark();  // Dog固有のメソッドを安全に呼び出せます
}

Cat* cat = dynamic_cast<Cat*>(animal);  // 失敗:nullptrを返します

重要: dynamic_cast はポリモーフィックな型(少なくとも1つの仮想関数を持つクラス)に対してのみ機能します。typeid 演算子を使用すると、オブジェクトの実際の型を照会できます:

#include <typeinfo>

Animal* pet = new Dog();
std::cout << typeid(*pet).name() << std::endl;  // Dogの型情報を出力します

dynamic_cast は便利ですが、頻繁な使用は多くの場合、設計上の問題を示唆しています。可能な限り仮想関数を優先してください。それにより、明示的な型チェックを行わずに、オブジェクトが型固有の動作を処理できるようになります。

challenge icon

チャレンジ

簡単

dynamic_cast を使用して、異なる車両タイプを安全に識別し、操作する車両検査システムを構築しましょう。検査官が特定の車両のみがサポートするタイプ固有のチェックを実行する必要がある、車両の階層構造を作成します。

コードは以下の3つのファイルに分けて構成します:

  • Vehicle.h: システム内の任意の車両を表す基底クラス Vehicle を定義します:
    • protected メンバ std::string licensePlate
    • ナンバープレートを初期化するコンストラクタ
    • "Vehicle: <licensePlate>" を返す仮想メソッド getDescription()
    • 仮想デストラクタ
  • Vehicles.h: 3つの派生車両タイプを定義します:

    Car:

    • private メンバ int seatCount
    • ナンバープレートと座席数を受け取るコンストラクタ
    • getDescription() をオーバーライドして "Car: <licensePlate>" を返す
    • Inspecting <seatCount> seatbelts in <licensePlate> と出力するメソッド inspectSeatbelts()

    Truck:

    • private メンバ double cargoCapacity (トン単位)
    • ナンバープレートと積載量を受け取るコンストラクタ
    • getDescription() をオーバーライドして "Truck: <licensePlate>" を返す
    • Inspecting cargo area (<cargoCapacity> tons) in <licensePlate> と出力するメソッド inspectCargo()

    Motorcycle:

    • private メンバ bool hasSidecar
    • ナンバープレートとサイドカーの有無を受け取るコンストラクタ
    • getDescription() をオーバーライドして "Motorcycle: <licensePlate>" を返す
    • サイドカーがある場合は Inspecting helmet storage in <licensePlate>、ない場合は No helmet storage in <licensePlate> と出力するメソッド inspectHelmetStorage()
  • main.cpp: 3つの入力(それぞれ別の行)を読み取ります:
    1. 乗用車(Car)のナンバープレート
    2. トラック(Truck)のナンバープレート
    3. オートバイ(Motorcycle)のナンバープレート

    4つの座席を持つ Car、10.5トンの積載量を持つ Truck、サイドカー付きの Motorcycle を作成します。これら3つすべてを Vehicle* ポインタの配列に格納します。

    配列をループで回し、各車両に対して以下を行います:

    1. getDescription() を使用してその説明を出力する
    2. dynamic_cast を使用して、各派生型へのキャストを試みる
    3. Car* へのキャストが成功した場合は、inspectSeatbelts() を呼び出す
    4. Truck* へのキャストが成功した場合は、inspectCargo() を呼び出す
    5. Motorcycle* へのキャストが成功した場合は、inspectHelmetStorage() を呼び出す

    各車両の検査の間に空行を出力してください。終了時には、動的に割り当てたオブジェクトをクリーンアップしてください。

例えば、入力が ABC-123TRK-456MTR-789 の場合:

Car: ABC-123
Inspecting 4 seatbelts in ABC-123

Truck: TRK-456
Inspecting cargo area (10.5 tons) in TRK-456

Motorcycle: MTR-789
Inspecting helmet storage in MTR-789

実際のオブジェクトの型がターゲットの型と一致する場合にのみ、dynamic_cast が有効なポインタを返すことに注目してください。各車両について、3つのキャストのうち1つだけが成功し、タイプ固有の検査メソッドを安全に呼び出すことができます。これが RTTI(実行時型情報)の力であり、実行時に実際の型を特定し、それに応じて行動することができます。

チートシート

C++は、実行時にオブジェクトの実際の型を特定し、継承階層内でポインタを安全に変換するために、RTTI (実行時型情報)dynamic_castを提供しています。

dynamic_castは実行時チェックを行い、変換が無効な場合はnullptrを返します:

class Animal {
public:
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void bark() { std::cout << "Woof!" << std::endl; }
};

Animal* animal = new Dog();
Dog* dog = dynamic_cast<Dog*>(animal);  // 成功:有効なポインタを返す

if (dog) {
    dog->bark();  // Dog固有のメソッドを安全に呼び出せる
}

Cat* cat = dynamic_cast<Cat*>(animal);  // 失敗:nullptrを返す

重要: dynamic_castはポリモーフィックな型(少なくとも1つの仮想関数を持つクラス)に対してのみ機能します。

typeid演算子は、オブジェクトの実際の型を照会します:

#include <typeinfo>

Animal* pet = new Dog();
std::cout << typeid(*pet).name() << std::endl;  // Dogの型情報を出力する

dynamic_castは便利ですが、頻繁に使用することは設計上の問題を示唆していることがよくあります。可能な限り仮想関数を優先してください。

自分で試してみよう

#include <iostream>
#include <string>
#include "Vehicle.h"
#include "Vehicles.h"

using namespace std;

int main() {
    // 入力を読み込む
    string carPlate, truckPlate, motorcyclePlate;
    cin >> carPlate;
    cin >> truckPlate;
    cin >> motorcyclePlate;

    // TODO: 4つの座席を持つ Car を作成する
    // TODO: 10.5トンの積載量を持つ Truck を作成する
    // TODO: サイドカー付き (true) の Motorcycle を作成する

    // TODO: これら3つすべてを Vehicle* ポインタの配列に格納する

    // TODO: 配列をループで回し、各車両に対して以下を行う:
    //   1. getDescription() を使用してその説明を表示する
    //   2. dynamic_cast を使用して各派生型へのキャストを試みる
    //   3. Car* へのキャストに成功した場合は、inspectSeatbelts() を呼び出す
    //   4. Truck* へのキャストに成功した場合は、inspectCargo() を呼び出す
    //   5. Motorcycle* へのキャストに成功した場合は、inspectHelmetStorage() を呼び出す
    //   6. 各車両の点検の間に空行を表示する

    // TODO: 動的に割り当てられたオブジェクトをクリーンアップする

    return 0;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン