Menu
Coddy logo textTech

不透明な構造体の定義

CoddyのCジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 18/61。

opaque ポインターの概念を理解したところで、パターンを完成させる方法を見てみましょう。ヘッダーでは型が存在することを宣言しますが、ソースファイルでは実際にその中身を定義します。

完全なセットアップは次のとおりです。まず、前方宣言だけを含むヘッダです。

// counter.h
#ifndef COUNTER_H
#define COUNTER_H

typedef struct Counter Counter;  // 不完全型

Counter *create_counter(int start);
void free_counter(Counter *c);

#endif

次に、ソースファイルにはactual 構造体定義が含まれています:

// 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 を include するファイルは Counter が存在することは認識しますが、valuestep を見ることはできません。main.cc->value と書こうとすると、コンパイラーエラーが発生します。members は完全に不可視だからです。

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

challenge icon

チャレンジ

簡単

Timerモジュールを作成して、opaque pointer patternを実演しましょう。structの定義をソースファイル内に隠し、モジュールを使用するコードから内部のmembersを完全に見えなくする方法を練習します。

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

  • timer.h:これは公開インターフェースです。forward declarationのみを使用してopaqueなTimer型をDeclareします。ここにはstruct bodyを記述しません。また、2つのfunctionもDeclareします。create_timer(seconds用のintegerを受け取り、Timer pointerをreturns)とfree_timer(Timer pointerを受け取り、後片付けを行う)です。symbol TIMER_Hを使用してinclude guardsを記述します。
  • timer.c:ここに秘密の実装があります。2つのHiddenなmembers、int secondsint running(runningには1、stoppedには0を使用)を持つactualなstruct TimerをDefineします。structをAllocateし、secondsをprovidedの値でInitializeし、runningを1に設定するconstructorをImplementします。memoryをFreeするdestructorもImplementします。
  • main.c:Timer moduleを使用してtimerを作成し、正常にcreatedされたことを確認します。structのmembersはHiddenなので、直接アクセスすることはできません。これがこの仕組みの目的です。作成を確認するmessageをPrintし、その後timerをFreeしてcleanup messageをPrintします。

入力として、timerをInitializeするsecondsの数(integer)を1つ受け取ります。

main fileで、providedのsecondsの値を使ってtimerを作成します。creation messageをPrintし、その後timerをFreeしてconfirmationをPrintします。

次の形式でoutputをPrintします。

Timer created
Timer freed

ここでの重要な洞察は、main.cはTimer pointersを扱えますが、secondsrunningに直接アクセスする方法はないということです。main.cでt->secondsを書こうとすると、struct bodyはtimer.cでのみDefineされているため、compiler errorが発生します。これがtrue encapsulationです。implementation detailsは、モジュールのusersから完全にHiddenになります。

自分で試してみよう

#include <stdio.h>
#include "timer.h"

int main() {
    int seconds;
    scanf("%d", &seconds);
    
    // TODO: 提供された秒数の値でタイマーを作成する
    
    // TODO: Print "Timer created"
    
    // TODO: タイマーを解放する
    
    // TODO: "Timer freed" を出力する
    
    return 0;
}
quiz icon腕試し

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

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

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