不透明な構造体の定義
CoddyのCジャーニー「Object Oriented Programming」セクションの一部 — レッスン 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言語におけるカプセル化の基礎です。次のステップは、ゲッター(getter)関数とセッター(setter)関数を通じて、制御されたアクセスを提供することです。
チャレンジ
簡単不透明ポインタ(opaque pointer)パターンを実演する Timer モジュールを構築しましょう。構造体の定義をソースファイル内に隠し、その内部メンバをモジュールを使用するコードから完全に不可視にする練習をします。
3つのファイルを作成します:
timer.h: これはパブリックインターフェースです。前方宣言のみを使用して不透明なTimer型を宣言します。ここには構造体の本体を記述しないでください!また、2つの関数を宣言します:create_timer(秒数を示す整数を受け取り、Timer ポインタを返す)とfree_timer(Timer ポインタを受け取り、クリーンアップを行う)です。インクルードガードにはTIMER_Hというシンボルを使用してください。timer.c: ここに秘密の実装を記述します。2つの隠しメンバint secondsとint running(実行中は 1、停止中は 0 を使用)を持つ実際のstruct Timerを定義します。コンストラクタを実装して構造体を割り当て、secondsを提供された値で初期化し、runningを 1 に設定します。デストラクタを実装してメモリを解放します。main.c: Timer モジュールを使用してタイマーを作成し、正常に作成されたことを確認します。構造体のメンバは隠されているため、直接アクセスすることはできません。これがこのパターンの要点です!作成を確認するメッセージを単に表示し、タイマーを解放してクリーンアップメッセージを表示してください。
入力として、タイマーを初期化するための 秒数(整数)を1つ受け取ります。
メインファイルで、提供された秒数の値を使用してタイマーを作成します。作成メッセージを表示し、タイマーを解放して確認メッセージを表示してください。
出力は以下の形式で表示してください:
Timer created
Timer freedここでの重要な洞察は、main.c は Timer ポインタを扱うことはできますが、seconds や running に直接アクセスする方法はないということです。main.c で t->seconds と記述しようとすると、構造体の本体が timer.c でしか定義されていないため、コンパイルエラーが発生します。これが真のカプセル化であり、実装の詳細はモジュールの利用者から完全に隠蔽されます。
チートシート
不透明ポインタ(opaque pointer)パターンは、型の宣言とその定義を分離し、実装の詳細を隠蔽します。
ヘッダーファイルには前方宣言のみが含まれます:
// 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);
}
構造体の定義は .c ファイルにのみ記述されます。ヘッダーをインクルードするファイルは、その型が存在することは知っていますが、そのメンバに直接アクセスすることはできません。これにより、C言語において真のカプセル化が実現されます。
自分で試してみよう
#include <stdio.h>
#include "timer.h"
int main() {
int seconds;
scanf("%d", &seconds);
// TODO: 提供された seconds の値を使用してタイマーを作成する
// TODO: "Timer created" と出力する
// TODO: タイマーを解放する
// TODO: "Timer freed" と出力する
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。