継承
次はオブジェクト指向の2つ目、「継承」のCによる実現です。「継承(Inheritance)」とはあるデータの属性/操作をそのまま他のデータが受け継ぐことです。ここまで作ってきたCarに加え、新たにBusを定義します。
#ifndef BUS_H__ #define BUS_H__ typedef Bus_type* Bus; Bus bus_new(int capacity, int mileage); void bus_delete(Bus object); void bus_addPassengers(Bus object, int howmany); /* howmany[人]乗車 */ void bus_removePassengers(Bus object, int howmany); /* howmany[人]降車 */ int bus_passengers(Bus object); /* 乗客数 */ #endif
さて、Bus(バス)もCar(車)の一種ですから、BusはCarを継承したい、すなわち下記のように、Carに対する操作(関数:car_xxxx)をそのままBusにも使いたいのです。
Bus b = bus_new(100, 5);
car_refuel((Car)b, 100); /* 給油 */
:
:
そのためにはstruct Bus_type内のメンバの型と並びがstruct Car_typeと一致していなければなりません。Car_typeは、
strcut Car_type {
int capacity_; /* タンク容量[L] */
int remain_; /* ガソリン残量[L] */
int mileage_; /* 燃費[L/km] */
int distance_; /* 走行距離[km] */
};
ですから、Bus_typeは、
strcut Bus_type {
int capacity_; /* タンク容量[L] */
int remain_; /* ガソリン残量[L] */
int mileage_; /* 燃費[L/km] */
int distance_; /* 走行距離[km] */
int passengers_ /* 乗客数: 新たに追加 */
};
となります。こうなっていればBusをCarにキャストして関数car_xxxxに与えたとき、car_xxxxはあたかもCar_type*であるかのように扱ってくれます。
とはいえBus_type、Car_typeの各メンバを上記のように直書きしてはミスも起こるでしょう。そのため、
int capacity_; /* タンク容量[L] */ int remain_; /* ガソリン残量[L] */ int mileage_; /* 燃費[L/km] */ int distance_; /* 走行距離[km] */
… #include "Car.h" struct Car_type { #include "Car.member" }; …
#include "Car.member" int passengers_; /* 乗客数 */
: : #include "Bus.h" struct Bus_type { #include "Bus.member" }; : :
とします。このとき、Car_typeとBus_typeの各メンバ名は、重複が許されないことに気をつけてください。重複しているとコンパイル・エラーとなってしまいます。
もう1つの問題が初期化(xxx_new)と後始末(xxx_delete)に残っています。関数bus_newは必要なメモリを確保の後、Carの初期化を行わなければなりません。が、かと言ってそのままcar_newを呼ぶわけにはいきません。car_newもまたCarに必要なメモリを確保してしまうので、そのまま呼ぶと使いもしないのにメモリを食いつぶします。
これに対処するため、CarおよびBusの初期化をそれぞれ二段構えにします。
/* Carの初期化 */ void car_ctor(Car car, int capacity, int mileage) { car->capacity_ = capacity; car->mileage_ = mileage; … } Car car_new(int capacity, int mileage) { Car car = malloc(sizeof(struct Car_type)); if ( car != NULL ) { car_ctor(car, capacity, mileage); } return car; } /* Busの初期化 */ void bus_ctor(Bus bus, int capacity, int mileage) { car_ctor((Car)bus, capacity, mileage); /* Carの初期化 */ bus->passengers = 0; } Bus bus_new(int capacity, int mileage) { Bus bus = malloc(sizeof(struct Bus_type)); if ( bus != NULL ) { bus_ctor(bus, capacity, mileage); } return bus; } /* 後始末も同様に… */ void car_dtor(Car car) { /* Carの後始末をここに書く */ } void car_delete(Car car) { car_dtor(car); free(car); } void bus_dtor(Bus bus) { car_dtor((Car)bus); /* Busの後始末をここに書く */ } void bus_delete(Bus bus) { bus_dtor(bus); free(bus); }
では実際にBusを動かしてみましょう。
#include <stdio.h> #include "Car.h" #include "Bus.h" int main() { Bus b; b = bus_new(50, 2); /* 50[L], 2[L/km] */ car_refuel((Car)b, 40); /* 40[L]給油 */ car_drive((Car)b, 5); /* 5[km]走行 */ bus_addPassengers(b,5); /* 5名乗車 */ car_drive((Car)b, 2); /* 2[km]走行 */ printf("distance = %d[km], remain = %d[L], passengers =%d\n", car_distance((Car)b), car_remain((Car)b), bus_passengers(b)); bus_delete(b); return 0; }
BusがCarとしても正しく機能してくれたでしょうか。
まとめ(…宿題?)
Opaque-typeとメンバの結合によって手続き型言語であるCでもオブジェクト指向のエッセンス「抽象データ型(Abstract Data Type)」と「継承(Inheritance)」を(やや不格好ではありますが)実現できました。
が、もうひとつ「多態(Polymorphism)」が残っています。
多態とはオブジェクトに応じて関数の中身がすり替わります。上記 Car/Bus を例にとれば、Busを運転する場合、普通乗用車と違って燃費が乗客数に応じて変化するため、car_driveでの処理を Carとは違うものにすり替えたいわけです。
これを実現するには構造体の中に「関数へのポインタ」を持たせ、関数car_driveはそのポインタを介して関数を呼び出します。そして初期化の際にCarとBusとで異なる関数のポインタを設定しておけばいい(実際にC++コンパイラはそんなコードを生成しています)…。言うのは簡単ですがやってみるとえらく面倒だし可読性を著しく損なってしまいます。
腕に覚えのあるCプログラマのアナタ、挑戦してみませんか?
