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>"を返すvirtualなgetDescription()メソッド- virtualデストラクター
- 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*ポインターのarrayに格納します。arrayをループし、各車両について次の処理を行います。
getDescription()を使用して説明を出力するdynamic_castを使用して各派生タイプへのcastingを試みるCar*へのcastingに成功したら、inspectSeatbelts()を呼び出すTruck*へのcastingに成功したら、inspectCargo()を呼び出すMotorcycle*へのcastingに成功したら、inspectHelmetStorage()を呼び出す
各車両の検査の間に空行を出力します。処理が完了したら、dynamically allocatedされたオブジェクトをCleanします。
たとえば、入力が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-789dynamic_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;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタパラメータ付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ徹底解説Rule of Three / Five / Zero復習 - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () の演算子オーバーロード型変換演算子復習 - Matrixクラス自分で練習してみよう: C++オンラインコンパイラ