Menu
CoddyTech

不透明な構造体の定義

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におけるカプセル化の基盤です。次のステップでは、ゲッター関数とセッター関数を通じて、制御されたアクセスを提供します。

challenge icon

チャレンジ

簡単

不透明ポインタパターンを示す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;
}
quiz icon腕試し

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

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Cオンラインコンパイラ