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 は便利ですが、頻繁な使用は多くの場合、設計上の問題を示唆しています。可能な限り仮想関数を優先してください。それにより、明示的な型チェックを行わずに、オブジェクトが型固有の動作を処理できるようになります。
チャレンジ
簡単dynamic_cast を使用して、異なる車両タイプを安全に識別し、操作する車両検査システムを構築しましょう。検査官が特定の車両のみがサポートするタイプ固有のチェックを実行する必要がある、車両の階層構造を作成します。
コードは以下の3つのファイルに分けて構成します:
Vehicle.h: システム内の任意の車両を表す基底クラスVehicleを定義します:- protected メンバ
std::string licensePlate - ナンバープレートを初期化するコンストラクタ
"Vehicle: <licensePlate>"を返す仮想メソッドgetDescription()- 仮想デストラクタ
- protected メンバ
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()
- private メンバ
main.cpp: 3つの入力(それぞれ別の行)を読み取ります:- 乗用車(Car)のナンバープレート
- トラック(Truck)のナンバープレート
- オートバイ(Motorcycle)のナンバープレート
4つの座席を持つ
Car、10.5トンの積載量を持つTruck、サイドカー付きのMotorcycleを作成します。これら3つすべてをVehicle*ポインタの配列に格納します。配列をループで回し、各車両に対して以下を行います:
getDescription()を使用してその説明を出力するdynamic_castを使用して、各派生型へのキャストを試みるCar*へのキャストが成功した場合は、inspectSeatbelts()を呼び出すTruck*へのキャストが成功した場合は、inspectCargo()を呼び出すMotorcycle*へのキャストが成功した場合は、inspectHelmetStorage()を呼び出す
各車両の検査の間に空行を出力してください。終了時には、動的に割り当てたオブジェクトをクリーンアップしてください。
例えば、入力が ABC-123、TRK-456、MTR-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;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
8ポリモーフィズム
コンパイル時 vs 実行時ポリモーフィズム関数オーバーロード仮想関数の再考純粋仮想関数抽象クラスC++におけるインターフェース設計Dynamic Casting & RTTIまとめ:図形計算機3コンストラクタとデストラクタ
デフォルトコンストラクタ引数付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ詳解Rule of Three / Five / Zeroまとめ - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () 演算子のオーバーロード型変換演算子まとめ:Matrix クラス