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>"を返すvirtualなgetDescription()メソッド
    • virtualデストラクター
  • 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*ポインターのarrayに格納します。

    arrayをループし、各車両について次の処理を行います。

    1. getDescription()を使用して説明を出力する
    2. dynamic_castを使用して各派生タイプへのcastingを試みる
    3. Car*へのcastingに成功したら、inspectSeatbelts()を呼び出す
    4. Truck*へのcastingに成功したら、inspectCargo()を呼び出す
    5. Motorcycle*へのcastingに成功したら、inspectHelmetStorage()を呼び出す

    各車両の検査の間に空行を出力します。処理が完了したら、dynamically allocatedされたオブジェクトをCleanします。

たとえば、入力が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つのcastingのうち1つだけが成功するため、タイプ固有の検査メソッドを安全に呼び出せます。これがRTTIの力です。実行時に実際のタイプを判定し、それに応じて処理できます。

自分で試してみよう

#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: サイドカー付きのMotorcycleを作成する (true)

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

    // TODO: 配列をループして各vehicleについて:
    //   1. getDescription()を使用してその説明を出力する
    //   2. dynamic_castを使用して各派生型へのキャストを試みる
    //   3. Car*へのキャストが成功した場合、inspectSeatbelts()を呼び出す
    //   4. Truck*へのキャストが成功した場合、inspectCargo()を呼び出す
    //   5. If cast to Motorcycle* succeeds, call inspectHelmetStorage()
    //   6. 各vehicleの検査の間に空行を出力する

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

    return 0;
}
quiz icon腕試し

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

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

自分で練習してみよう: C++オンラインコンパイラ