Opaque-type(不透明な型)の導入
この問題を解決する方法として、1つには、void*を利用するという解があります。
#ifndef CAR_H__ #define CAR_H__ typedef void* Car; Car car_new(int capacity, int mileage); /* 納車 */ void car_delete(Car car); /* 廃車 */ int car_distance(Car car); /* 走行距離 */ void car_refuel(Car car, int amount); /* amount[L]給油 */ … #endif
#include <stdlib.h> /* malloc, free */ #include "Car.h" struct Car_type { int capacity_; /* タンク容量[L] */ int remain_; /* ガソリン残量[L] */ int mileage_; /* 燃費[L/km] */ int distance_; /* 走行距離[km] */ }; Car car_new(int capacity, int mileage) { struct Car_type* car = malloc(sizeof(struct Car_type)); if ( car != NULL ) { car->capacity_ = capacity; … } return car; } void car_delete(Car car) { free(car); } int car_distance(Car car) { struct Car_type* ct = (struct Car_type*)car; return ct->distance_; } … その他モロモロ、Carに対する操作…
これで利用者にはCar_typeそのものを見せることなく、操作(関数)だけで車を定義できました。しかし、これではコードの安全性に問題が残ります。このインターフェイスではCarはvoid*なのですから、関数car_newによって確保/初期化された「正しい車」でなくとも、ポインタであればCarだとみなされてしまいます。
char* myCar = "車のつもり"; car_refuel(myCar, 50); /* コンパイルできちゃう! */
これではあまりに危なっかしいので、typedef void* Car;を改め、
#ifndef CAR_H__ #define CAR_H__ typedef struct Car_type* Car; … #endif
とします。Cは型Xが未知であっても、関数の引数や戻り値が(Xでなく)X*ならば問題なくコンパイルできてしまうんです。利用者にはヘッダ:「Car.h」と「Car.c」をコンパイルしたライブラリが提供されます。こうすることで実装の詳細を隠し、抽象データ型として使ってもらえます。
ただしこのスタイルでは、Car_typeを利用者に公開しないのでautomatic変数とすることができず、使用開始時に必ずcar_new、最後に忘れずcar_deleteを呼ばなくてはならない点に注意してください。
