2013年12月15日日曜日

C言語でポリモーフィズム(polymorphism)を実現

C言語にはオブジェクト指向言語のようにオブジェクトに
メソッド(手続き)を持ち運ぶ手段は提供されていない。

ただし、関数ポインタを高階関数として利用すれば似たようなことはできる。
オブジェクト指向言語ではないので、ダブルディスパッチを用いて
目的を達すると言ったほうが適切かもしれない。

デザインパターンであるvisitor(ビジター)パターンは
オブジェクト指向言語でしばしば利用される。
しかしこれは決してJavaやC++だけで利用できるパターンではない。
これを非オブジェクト指向言語であるCで実装してみる。


visitor パターンとは
(wikipediaより)
オブジェクト指向プログラミング およびソフトウェア工学 において、
アルゴリズムをオブジェクトの構造から分離するためのデザインパターンである。
分離による実用的な結果として、既存のオブジェクトに対する新たな操作を
構造を変更せずに追加することができる。

さて、パターンの定義はややこしいが、意味と役割は非常に分かりやすい。
難しく考える必要はない。一言でこのパターンの利用用途を表すと下記になる。

visitする関数、visitされる関数がどちらも部品として取り替える必要がある場合に使う。
- visitorする側は、visit()メソッドを実装する。
- visitされる側は、accept(visitor)メソッドを実装する。


関数ポインタが不慣れな場合もあるだろう。
基礎から復習を始める。

◆ 関数ポインタについて
// int型を引数に取るvoid型を返す(何も返さない)、関数へのポインタを宣言
void (*fp)(int); // void (*fp)(); でよい

// 適当な関数を作成
void my_func(int n) {
printf("%d\n", n);
}

// 関数の先頭アドレスをポインタへ代入
fp = my_func; // &my_funcでもよい

// 呼び出し
(*fp)(10);



復習を終えたところでvisitorパターンのコードを書いてみる。

◆ サンプル例
#include <stdio.h>

void (*visit)();
void (*accept)(void (*visit)()); // void (*accept)();でよい


void accept1(void (*visit)()) {
puts("accept1");
(*visit)();
}

void accept2(void (*visit)()) {
puts("accept2");
(*visit)();
}

void visit1() {
puts("visit1");
}

void visit2() {
puts("visit2");
}


int main() {
accept = accept1;
visit = visit1;
accept(visit);

accept = accept2;
visit = visit2;
accept(visit);

return 0;
}


結果はこうなる。
accept1
visit1
accept2
visit2



関数ポインタのプロトタイプ宣言(typedef)をしておけば
可読性があがるだろうか。

◆ サンプル例(typedef版)
#include <stdio.h>

// 関数ポインタをtypedefで定義
typedef void (*visit_type)();

typedef void (*accept_type)(visit_type); // typedef void (*accept_type)();でよい


void accept1(visit_type visit) {
puts("accept1");
visit();
}

void accept2(visit_type visit) {
puts("accept2");
visit();
}

void visit1() {
puts("visit1");
}

void visit2() {
puts("visit2");
}


int main() {
// 型の変数を宣言
visit_type visit;
accept_type accept;

accept = accept1;
visit = visit1;
accept(visit);

accept = accept2;
visit = visit2;
accept(visit);

return 0;
}