アップキャスト
CoddyのCジャーニー「Object Oriented Programming」セクションの一部 — レッスン 31/61。
アップキャストとは、特定の(派生)型をより一般的な(基底)型として扱う行為です。C言語のコンポジションモデルでは、これは基底型を埋め込んでいる構造体へのポインタを、その基底型を期待する関数に渡すことを意味します。
Vehicleという基本構造体と、それを含むCarを考えてみましょう:
typedef struct {
int speed;
} Vehicle;
typedef struct {
Vehicle vehicle; // 最初のメンバとしてのベース
int doors;
} Car;では、あらゆる乗り物で動作する汎用的な関数を想像してみてください:
void accelerate(Vehicle* v, int amount) {
v->speed += amount;
}最初のメンバールールのおかげで、Car*をVehicle*にキャストすることで、この関数に渡すことができます:
Car my_car = {{0}, 4}; // speed=0, doors=4
accelerate((Vehicle*)&my_car, 50);
printf("Speed: %d\n", my_car.vehicle.speed); // 出力: 50これはアップキャストです。つまり、具体的な型から汎用的な型へと「上」に移動しています。関数 accelerate は、自分が Car を扱っていることを認識していません。それは Vehicle としてのみ認識します。これにより、基本型に対して動作しつつ、それを埋め込んでいる任意の派生型を受け入れる、再利用可能なコードを記述できるようになります。
チャレンジ
簡単アップキャスティングの動作を実証する、シンプルな交通システムを構築しましょう。ベースとなる Vehicle 型と、より具体的な Motorcycle 型を作成し、ポインタのキャストを通じて汎用関数が両方の型でどのように機能するかを示します。
アップキャスティングの利点は、ベース型に対して関数を記述すれば、その型を埋め込んでいる派生型でもシームレスに動作することです。オートバイは乗り物(Vehicle)の一種であるため、Vehicle* を受け取る関数はすべて、オートバイを処理できるはずです。
コードは以下の3つのファイルに分けて構成します:
vehicle.h: インクルードガードを使用して構造体の階層を定義します。speed(現在の速度を表す整数)とmax_speed(車両の最高速度を表す整数)の2つのフィールドを持つVehicle構造体を作成します。次に、Vehicleを最初のメンバとして埋め込み、has_sidecarフィールド(1は「はい」、0は「いいえ」を表す整数)を追加したMotorcycle構造体を定義します。2つの関数を宣言します:Vehicle*と速度を上げる量を受け取るboost関数、およびMotorcycle*を受け取るprint_motorcycle関数です。vehicle.c: 両方の関数を実装します。boost関数は、車両の速度を指定された量だけ増加させますが、max_speedを上限とします。新しい速度が最高速度を超える場合は、代わりにmax_speedに設定してください。print_motorcycle関数は、埋め込まれた車両データを含むオートバイのすべての情報を表示する必要があります。main.c:Motorcycleを作成し、すべてのフィールドを初期化します。次に、boost関数を呼び出してアップキャスティングを実証します。この汎用関数に渡すために、Motorcycle*をVehicle*にキャストしてください。ブースト後、オートバイの情報を出力して結果を確認します。
オートバイの初期速度、最高速度、サイドカーの有無(1または0)、およびブースト量の4つの入力を受け取ります。
出力は以下のようになるはずです:
Speed: 75
Max Speed: 120
Sidecar: Yesここで、75 はブースト後の速度(初期 50 + ブースト 25)、120 は最高速度、Yes または No はサイドカーの有無を示します。ブーストによって速度が最高速度を超える場合は、速度は最高速度で制限される必要があります。
ここでの重要な学習ポイントは、boost 関数はオートバイについて何も知らないということです。それは Vehicle に対してのみ機能します。しかし、アップキャスティングを通じて、オートバイはこの汎用関数を使用して、埋め込まれた車両データを変更することができます。
チートシート
アップキャスティングは、基本型を埋め込んだ構造体へのポインタを、その基本型を期待する関数に渡すことで、特定の(派生)型をより一般的な(基本)型として扱います。
基本構造体と、それを最初のメンバとして埋め込む派生構造体を定義します:
typedef struct {
int speed;
} Vehicle;
typedef struct {
Vehicle vehicle; // Base as first member
int doors;
} Car;基本型を扱う関数を作成します:
void accelerate(Vehicle* v, int amount) {
v->speed += amount;
}派生型のポインタを基本型のポインタにキャストして、関数を呼び出します:
Car my_car = {{0}, 4}; // speed=0, doors=4
accelerate((Vehicle*)&my_car, 50);
printf("Speed: %d\n", my_car.vehicle.speed); // Output: 50これにより、基本型を埋め込んだ任意の派生型を受け入れつつ、基本型に対して動作する再利用可能なコードを記述できます。
自分で試してみよう
#include <stdio.h>
#include "vehicle.h"
int main() {
int initial_speed, max_speed, has_sidecar, boost_amount;
// 入力を読み込む
scanf("%d", &initial_speed);
scanf("%d", &max_speed);
scanf("%d", &has_sidecar);
scanf("%d", &boost_amount);
// TODO: Motorcycle を作成し、すべてのフィールドを初期化する
// 注意: Motorcycle は最初のメンバとして Vehicle を埋め込んでいる
// TODO: boost を呼び出してアップキャストを実演する
// Motorcycle* を Vehicle* にキャストして boost 関数に渡す
// TODO: オートバイの情報を表示する
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。