SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

Cで実現する「ぷちオブジェクト指向」

「抽象データ型」と「継承」を実現するコーディングスタイル


継承

 次はオブジェクト指向の2つ目、「継承」のCによる実現です。「継承(Inheritance)」とはあるデータの属性/操作をそのまま他のデータが受け継ぐことです。ここまで作ってきたCarに加え、新たにBusを定義します。

Bus.h
#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(車)の一種ですから、BusCarを継承したい、すなわち下記のように、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_ /* 乗客数: 新たに追加 */
};

 となります。こうなっていればBusCarにキャストして関数car_xxxxに与えたとき、car_xxxxはあたかもCar_type*であるかのように扱ってくれます。

 とはいえBus_typeCar_typeの各メンバを上記のように直書きしてはミスも起こるでしょう。そのため、

Car.member
int capacity_; /* タンク容量[L]   */
int remain_;   /* ガソリン残量[L] */
int mileage_;  /* 燃費[L/km]      */
int distance_; /* 走行距離[km]    */
Car.c
#include "Car.h"

struct Car_type {
#include "Car.member"
};
…
Bus.member
#include "Car.member"
  int passengers_; /* 乗客数 */
Bus.c
 :
 :
#include "Bus.h"

struct Bus_type {
#include "Bus.member"
};
 :
 :

 とします。このとき、Car_typeBus_typeの各メンバ名は、重複が許されないことに気をつけてください。重複しているとコンパイル・エラーとなってしまいます。

 もう1つの問題が初期化(xxx_new)と後始末(xxx_delete)に残っています。関数bus_newは必要なメモリを確保の後、Carの初期化を行わなければなりません。が、かと言ってそのままcar_newを呼ぶわけにはいきません。car_newもまたCarに必要なメモリを確保してしまうので、そのまま呼ぶと使いもしないのにメモリを食いつぶします。

 これに対処するため、CarおよびBusの初期化をそれぞれ二段構えにします。

Car.c/Bus.c
/* 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を動かしてみましょう。

simulate.c
#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;
}

 BusCarとしても正しく機能してくれたでしょうか。

まとめ(…宿題?)

 Opaque-typeとメンバの結合によって手続き型言語であるCでもオブジェクト指向のエッセンス「抽象データ型(Abstract Data Type)」と「継承(Inheritance)」を(やや不格好ではありますが)実現できました。

 が、もうひとつ「多態(Polymorphism)」が残っています。

 多態とはオブジェクトに応じて関数の中身がすり替わります。上記 Car/Bus を例にとれば、Busを運転する場合、普通乗用車と違って燃費が乗客数に応じて変化するため、car_driveでの処理を Carとは違うものにすり替えたいわけです。

 これを実現するには構造体の中に「関数へのポインタ」を持たせ、関数car_driveはそのポインタを介して関数を呼び出します。そして初期化の際にCarBusとで異なる関数のポインタを設定しておけばいい(実際にC++コンパイラはそんなコードを生成しています)…。言うのは簡単ですがやってみるとえらく面倒だし可読性を著しく損なってしまいます。

 腕に覚えのあるCプログラマのアナタ、挑戦してみませんか?

修正履歴

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
特集記事連載記事一覧

もっと読む

この記事の著者

επιστημη(エピステーメー)

C++に首まで浸かったプログラマ。Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"りわんくま同盟でたまにセッションスピーカやったり中国茶淹れてにわか茶...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/885 2007/02/14 10:06

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー