Menu
Coddy logo textTech

不透明な構造体の定義

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 が存在することは知っていますが、valuestep を見ることはできません。main.c 内で c->value と記述しようとするとコンパイルエラーが発生します。つまり、メンバーは完全に不可視です。

この分離は、C言語におけるカプセル化の基礎です。次のステップは、ゲッター(getter)関数とセッター(setter)関数を通じて、制御されたアクセスを提供することです。

challenge icon

チャレンジ

簡単

不透明ポインタ(opaque pointer)パターンを実演する Timer モジュールを構築しましょう。構造体の定義をソースファイル内に隠し、その内部メンバをモジュールを使用するコードから完全に不可視にする練習をします。

3つのファイルを作成します:

  • timer.h: これはパブリックインターフェースです。前方宣言のみを使用して不透明な Timer 型を宣言します。ここには構造体の本体を記述しないでください!また、2つの関数を宣言します:create_timer(秒数を示す整数を受け取り、Timer ポインタを返す)と free_timer(Timer ポインタを受け取り、クリーンアップを行う)です。インクルードガードには TIMER_H というシンボルを使用してください。
  • timer.c: ここに秘密の実装を記述します。2つの隠しメンバ int secondsint running(実行中は 1、停止中は 0 を使用)を持つ実際の struct Timer を定義します。コンストラクタを実装して構造体を割り当て、seconds を提供された値で初期化し、running を 1 に設定します。デストラクタを実装してメモリを解放します。
  • main.c: Timer モジュールを使用してタイマーを作成し、正常に作成されたことを確認します。構造体のメンバは隠されているため、直接アクセスすることはできません。これがこのパターンの要点です!作成を確認するメッセージを単に表示し、タイマーを解放してクリーンアップメッセージを表示してください。

入力として、タイマーを初期化するための 秒数(整数)を1つ受け取ります。

メインファイルで、提供された秒数の値を使用してタイマーを作成します。作成メッセージを表示し、タイマーを解放して確認メッセージを表示してください。

出力は以下の形式で表示してください:

Timer created
Timer freed

ここでの重要な洞察は、main.c は Timer ポインタを扱うことはできますが、secondsrunning に直接アクセスする方法はないということです。main.ct->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;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Object Oriented Programmingのすべてのレッスン