不透明な構造体の定義
CoddyのCジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 18/61。
不透明ポインターの概念を理解したところで、このパターンを完成させる方法を見てみましょう。ヘッダーファイルでは型が存在することを宣言しますが、実際の内容を定義するのはソースファイルです。
こちらがセットアップの全体です。まず、前方宣言のみを含むヘッダーです。
// counter.h
#ifndef COUNTER_H
#define COUNTER_H
typedef struct Counter Counter; // 不完全型
Counter *create_counter(int start);
void free_counter(Counter *c);
#endif
次に、ソースファイルには実際の構造体定義が含まれています。
// counter.c
#include "counter.h"
#include <stdlib.h>
struct Counter {
int value; // 外部からは見えません!
int step;
};
Counter *create_counter(int start) {
Counter *c = malloc(sizeof(Counter));
c->value = start;
c->step = 1;
return c;
}
void free_counter(Counter *c) {
free(c);
}
重要なポイント: struct Counter { ... }は.cファイルにのみ記述されています。counter.hをインクルードするファイルは、Counterが存在することはわかりますが、valueやstepを見ることはできません。main.cでc->valueと書こうとすると、コンパイルエラーが発生します。メンバーは本当に見えないのです。
この分離は、Cにおけるカプセル化の基盤です。次のステップでは、ゲッター関数とセッター関数を通じて、制御されたアクセスを提供します。
チャレンジ
簡単不透明ポインタパターンを示すTimerモジュールを作りましょう。構造体の定義をソースファイル内に隠し、モジュールを使うコードから内部メンバーが完全に見えないようにする方法を練習します。
次の3つのファイルを作成します。
timer.h: これは公開インターフェースです。前方宣言のみを使って不透明なTimer型を宣言します。ここに構造体本体を書いてはいけません。また、2つの関数を宣言します。create_timer(秒数を表す整数を受け取り、Timerポインタを返します)とfree_timer(Timerポインタを受け取り、後片付けをします)です。シンボルTIMER_Hを使ってインクルードガードを設定します。timer.c: ここに内部の実装を記述します。非公開メンバーを2つ持つ実際のstruct Timerを定義します。メンバーはint secondsとint runningです(実行中には1、停止中には0を使います)。構造体のメモリを確保し、secondsを渡された値で初期化して、runningを1に設定するコンストラクターを実装します。メモリを解放するデストラクターも実装します。main.c: Timerモジュールを使ってタイマーを作成し、正常に作成されたことを確認します。構造体のメンバーは隠されているため、直接アクセスすることはできません。これがまさに重要な点です。作成を知らせるメッセージを出力し、その後タイマーを解放して、後片付けを知らせるメッセージを出力します。
入力として、タイマーの初期化に使う秒数(整数)が1つ与えられます。
メインファイルで、与えられた秒数を使ってタイマーを作成します。作成メッセージを出力し、その後タイマーを解放して、確認メッセージを出力します。
次の形式で出力します。
Timer created
Timer freedここで重要なのは、main.cはTimerポインタを扱えますが、secondsやrunningに直接アクセスする方法はないという点です。main.cでt->secondsと書こうとすると、構造体本体はtimer.cでしか定義されていないため、コンパイルエラーになります。これが真のカプセル化です。実装の詳細は、モジュールを使う側から完全に隠されています。
自分で試してみよう
#include <stdio.h>
#include "timer.h"
int main() {
int seconds;
scanf("%d", &seconds);
// TODO: 指定された秒数の値を使ってタイマーを作成する
// TODO: "Timer created" を出力する
// TODO: タイマーを解放する
// TODO: "Timer freed" を出力する
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Cオンラインコンパイラ