.. _pattern_ftable: コードパターン: 構造体に埋め込まれた関数ポインタ ======================================================================= はじめに ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 開発者は同じ名前の操作関数の呼び出しで、データ毎に処理を変えるという要 求を持ちます。この要求はあまり一般的なので、新しい言語では文法レベルで そのような要求に応えています。オブジェクト指向言語で言うところのポリモ フィズムです。C言語にはそういった仕掛けは無いので開発者が独自にそのよ うな仕掛けを作る必要があります。しかし書き方はおおまかには決まっていて、 それをコードパターンととらえて読解に役立てることができます。 同じ型のデータに対して操作関数群が定義されている、 というコードパターン(:ref:`pattern_resource`) と 関数ポインタとコールバック関数(:ref:`callback`) を組合せて 実現することが多いです。 パターンに含まれる断片の役割 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, このコードパターンには(重複する箇所はありますが)3つの断片からなると考え ることができます。 .. figure:: ftable.svg 1. クライアントコード データの種類をできるだけ気にすることなく、データを操作したいという 立場にあるコードです。「仕掛け」の提供する外向けのインターフェイスを 利用します。 2. データの種類特有のコード(バックエンド) 「仕掛け」の提供する内向きのインターフェイスに 適合する形式でデータの種類に応じた操作を実現します。 (pattern1. 緑の部分) 文脈によってはプラグインを 呼んだりすることもあります。 3. 仕掛けを提供するコード 3.1 バックエンドの実装を既定する型 (内向きのインターフェイス) (pattern3. 青の部分) 仕掛けを作る側がバックエンドに期待 する実装形式を示します。 3.2 クライアントコード向けの操作関数(外向きのインターフェイス) (pattern2. 赤の部分) クライアントからのデータの初期化の依頼や データ操作を受けつけます。内部で、内向きのインターフェイス を通して具体的な処理をバックエンドに依頼します。 例: 長さを保持するデータと印字操作 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 単純な例を示します。「長さ」を保持するデータ構造と、その操作として保持 する長さを印字する関数を考えます。キロメートルの単位で距離を保持するデー タとメートルの単位で距離を保持するデータの2種類がありますが、印字の操 作にあたって、その種類を気にせず同じ関数で操作できます。 実際には「コードパターン」などと大袈裟なものを持ち出すこと無く 実現できますが、ここでは説明のためにこの「コードパターン」で実装してみ ました。 .. literalinclude:: length.c :language: c 断片に分解して見て行きましょう。 クライアントコード ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, .. code-block:: c int main(void) { struct Length *km = newLengthInKiloMeter ( 3 ); struct Length *m = newLengthInMeter ( 5 ); print_length(km, stdout); print_length(m, stdout); return 0; } newLengthInKiloMeterでキロメートルの単位で距離を保持する種類のデータを 作成しています。(3km) newLengthInMeterでメートルの単位で距離を保持する種類のデータを作成して います。(5m) この後それぞれを標準出力に印字していますが、ここでprint\_lengthという データの種類によらない操作関数を利用しています。 バックエンド ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, .. code-block:: c static int km_printer(struct Length * l, FILE *fp) { return fprintf(fp, "%dm\n", (l->L * 1000)); } static int m_printer(struct Length * l, FILE *fp) { return fprintf(fp, "%dm\n", l->L); } km\_printerは、キロメートルの単位で距離を保持する種類のデータ用の 印字関数です。 m\_printerは、メートルの単位で距離を保持する種類のデータ用の 印字関数です。 内向けのインターフェイス ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, .. code-block:: c struct Length { int L; int (* print) (struct Length * l, FILE *fp); }; Length構造体のうち、特に printフィールドの型の部分が 内向けのインターフェイスのインターフェイスとなります。 バックエンドとして実装された2つの関数は このprintフィールド の型に合致するよう実装されていました。 外向けのインターフェイス ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, .. code-block:: c int print_length (struct Length * l, FILE *fp) { return l->print(l, fp); } struct Length * newLengthInKiloMeter (int km) { struct Length *p = malloc (sizeof (struct Length)); p->L = km; p->print = km_printer; return p; } struct Length * newLengthInMeter (int m) { struct Length *p = malloc (sizeof (struct Length)); p->L = m; p->print = m_printer; return p; } print\_lengthは自分自身では具体的な印字処理をせず、関数ポインタとして printフィールドが指し示すコールバック関数の処理を依頼しています。 newLengthInKiloMeterとnewLengthInMeterはそれぞれデータの種類毎に printフィールドに適切なバックエンドの提供する関数を設定しています。 多数の操作関数を持つデータに対するより効率の良い実装 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 「長さ」の例は操作関数が1つ印字だけの単純なものでした。 操作の数が増えたとき、操作毎に関数ポインタを保持するための フィールドを追加していくと、データが大きくなってしまいます。 .. code-block:: c struct Length { int L; int (* print) (struct Length * l, FILE *fp); int (* read) (struct Length * l, FILE *fp); struct Length * (* copy) (struct Length * oirginal); ... }; こういった場合、関数ポインタのフィールドだけを集めて 独立したデータ構造((仮想)関数テーブル)を用意して、元の データからはその関数テーブルを指し示すようにしているのを 良く目にします。 .. code-block:: c struct Length { int L; struct LengthFunctions *ftable; } struct LengthFunctions { int (* print) (struct Length * l, FILE *fp); int (* read) (struct Length * l, FILE *fp); struct Length * (* copy) (struct Length * oirginal); ... };