Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

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

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

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2007/02/14 00:00

オブジェクト指向言語全盛の今なお、Cは現役で活躍しています。手続き型言語であるCでオブジェクト指向のエッセンス、「抽象データ型」と「継承」を実現するコーディングスタイルを試みます。

目次

はじめに

 CodeZineではお初にお目にかかります、επιστημη(エピステーメー)です。最初のアーティクルはクラシックなCのお話。

 昨今のアプリケーションはオブジェクト指向言語による実装が主流と言ってもいいでしょう。C++、Java、VB.NETさらにはRubyやPythonといったスクリプト言語まで、オブジェクト指向でない言語を探すのに苦労するくらいです。

 本記事では、今なお現役バリバリで活躍している手続き型言語の代表格(?)Cによる、オブジェクト指向のマネゴト(オブジェクト指向風味のCコーディングスタイル)を試みます。

対象読者

 もっぱらCを主な開発言語として使ってはいるけども、オブジェクト指向に興味と憧れを抱いている方。

抽象データ型

 手始めにオブジェクト指向の特徴の1つ、「抽象データ型(ADT:Abstract Data Type)」をCで実現してみます。抽象データ型とは、データの具体的な詳細に一切触れず、そのデータに対して可能な操作の集合でデータを定義するものです。

 例えば簡単な例、「自動車の燃費シミュレーション」を考えてみましょう。車(Car)の内部にはガソリンタンクがあり、その容量(capacity)とガソリンタンクに蓄えられたガソリンの残量(remain)をデータとして持っています。車を走らすとおおむねその距離に応じてガソリンを消費しつつ、走行距離を伸ばしていきます。

 ここまでを構造体と関数で表現すると、次のようになります。

Car.h
#ifndef CAR_H__
#define CAR_H__

struct Car_type {
  int capacity_; /* タンク容量[L]   */
  int remain_;   /* ガソリン残量[L] */
  int mileage_;  /* 燃費[L/km]      */
  int distance_; /* 走行距離[km]    */
};

typedef Car_type* Car;

void car_initialize(Car car, int capacity, int mileage);
void car_drive(Car car, int distance);

#endif

 以下、関数car_initializecar_driveはそれぞれ「与えられたタンク容量と燃費で車を初期化する」「与えられた距離(km)走行する」を行います。

Car.c
#include "Car.h"

void car_initialize(Car car, int capacity, int mileage) {
  car->capacity_ = capacity;
  car->mileage_ = mileage;
  car->remain_ = 0; /* ガス欠状態 */
  car->distance_ = 0; /* メータ・クリア */
}

void car_drive(Car car, int distance) {
  /* ガソリン残量以上には走れない… */
  while ( car->mileage_ * distance > car->remain_ ) {
    --distance;
  }
  car->distance_ += distance;
  car->remain_ -= car->mileage_ * distance;
}

 これらをmain関数から呼び出してみます。

simulate.c
#include <stdio.h>
#include "Car.h"

int main() {
  Car_type c;
  car_initialize(&c, 50, 2);
  c.remain_ = 40; /* 給油 */
  car_drive(&c, 5); /* 軽くドライブ */
  car_drive(&c, 2); /* さらにも少し */
  printf("%d[km], remain: %d[L]\n", c.distance_, c.remain_);
  return 0;
}

 実にオーソドックスです。程度の差こそあれ、おそらく多くのCアプリケーションはこれと似たコードで実装されていることでしょう。

安全性に問題のある車

 ここでいくつかの問題(不安要素)があります。1つはCar_typeの利用者(ここでは「simulate.c」の作者)はCar_typeが持つメンバの型と名前を知らないことには走行距離やガソリン残量をプリントできません。

 メンバの型と名前を知るということは、すなわちそれらメンバを勝手に書き換えることができるということです。そのため例えばタンク容量を超えて給油したり、ヨソ様の車からガソリンを失敬したりすることができてしまいます。さらに、車の設計者(「Car.h」/「Car.c」の設計/実装者)がメンバの名前/型を変更するたびに利用者はコードの変更を余儀なくされます。車を修理に出すたびに操作法が変わってしまうわけです。

 利用者が必要なCar_typeに対するすべての操作、ここでは給油や走行距離の取得などを関数で提供すれば、利用者はCar_typeの詳細を知ることも触れることもなく、提供された関数を使ってシミュレーションが可能です。

 ところが、利用者には依然としてCar_typeの中身が丸見え(アクセス可能)ですから、用意された関数を呼ばずとも直接メンバを参照/変更できてしまいます。利用者つまり「Car.h」を#includeしたコードにCar_typeの中身を隠すことはできないものでしょうか?


  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

修正履歴

  • 2007/02/14 10:06 「car.c」のソース修正:car->distance_; ⇒ distance_ = 0;

著者プロフィール

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

    C++に首まで浸かったプログラマ。 Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"り わんくま同盟でたまにセッションスピーカやったり 中国茶淹れてにわか茶人を気取ってたり、 あと Facebook とか。 著書: - STL標準...

All contents copyright © 2005-2019 Shoeisha Co., Ltd. All rights reserved. ver.1.5