前回、関数ポインタについて解説しました。このポインタは関数の型であるというのが、、大雑把な解説の根幹です。今回はこの関数ポインタを使った応用を解説していきます。
--
いきなりここに飛んで来ちゃった人は、よろしければ下記からご覧ください。


  • 関数ポインタの別名定義
関数ポインタ変数の宣言はこうでしたよね。

void (*pSleep)(int);
でもこれ、正直分かりづらいですよね。そこで思い出して頂きたいのが C言語020 構造体で説明した typedef です。これは別名定義という、分かりにくい定義に名前をつけることで簡単に使おうという命令です。そうです、関数の型に名前を付けてしまえば良いのではないかと。例えば、前回紹介した pSleep を型定義に変更すると、

typedef void (*FuncSleep)(int);
このように記述することで、新たに FuncSleep という名前の関数ポインタの型が出来ます。これを使えば、

FuncSleep pSleep;
どうでしょう、一気に分かりやすくなったのではないでしょうか。


  • 関数の型を引数に
関数の引数には、型と引数名を与えることが出来ます。関数ポインタは型です。ということは、関数の引数に関数ポインタを与えることも可能です。

void GreetingJapan(const char* pName) { printf("%sさん、こんにちは!\n", pName); }
void GreetingEnglish(const char* pName) { printf("Hello! Mx. %s\n", pName); }
void GreetingFrench(const char* pName) { printf("Bonjour %s-san !\n", pName); }
このように同じ型を持つ複数の関数があったとします。同じ型なので、別名定義で型定義してしまいます。

typedef void (*FuncGreeting)(const char*);
FuncGreeting の型を使えば、関数の引数に関数ポインタを用いることが出来るようになります。

void DrawGreeting(const char* pName, FuncGreeting printGreeting) { printGreeting(pName); }
呼び出し側で

DrawGreeting("Suzuki", GreetingEnglish); DrawGreeting("佐藤", GreetingJapan); DrawGreeting("Naito", GreetingFrench);
このように記述することで、同じ関数呼び出しでも異なる動作を指定することが出来ます。
動作が変わる
※ 空調服と言えばバートルでしょう。このところの暑さは洒落になりませんので、少しでも快適な服を選ぶべきだと考えます。

  • コールバック関数
さて、上記はあまり有用な使い方ではありませんでした。わざわざ関数ポインタを使わなくても、直接呼び出せば良いことですからね。この説明は本題であるコールバックを説明する前段階の知識として、説明しています。コールバックとは、呼び出し元に処理が途中で戻ってくることから命名されています。関数ポインタを使うことで、こういう少しトリッキーな使い方が出来るようになります。

突然ですがここで例え話です。例えば、比較って全てにおいて単純に比較できる…とは限りませんよね。或いは結果の受け取りが、戻り値一発で済む…とも限りません。比較の場合は時に構造体で、その中の一部のメンバだけが比較対象になるのかもしれません。戻り値受け取りは、毎回個数が違う場合だってあるかもしれません。

このような特殊な事例を解決するための手法がコールバックです。比較するのが単純ではないのであれば、その比較関数を呼び出すようにします。戻り値が複雑なのであれば、その状態を伝える関数を呼び出すのです。

この手の説明でだいたい使われるのが qsort 関数です。これは配列の並び替えを行う stdlib ライブラリで定義された関数です。この stdlib は配列の並び替えを行うのですが、その引数による指定がこんな事になっています。

void qsort( void* base, // 配列名 size_t num, // 要素数 size_t size, // 型のサイズ   int (* cmpFunc)(const void * n1, const void * n2));
void* という型が出てきました。これは型が不明のポインタの指定です。ポインタはアドレスを示していますから、少なくともアドレスのサイズは全ての型で共通です。だから void* なんていうポインタが存在しうるのです。ある意味 void* はキャストして使うのが前提のポインタです。

さて、問題は4つ目の引数 cmpFunc です。この cmpFunc は、配列要素の比較関数です。並び替えの比較基準が不明なので、比較専用の関数を並び替えの際に呼び出して、結果をもらうようにしています。単純に型が int だった場合は、n1 - n2 の結果を返却すると昇順、n2 - n1 の結果を返却すると降順で並び替えます。

実際に並び替えを実行してみます。並び替えの対象を文字列配列にしてみます。

char pName[][10] = { "Sato", "Suzuki", "Takahashi", "Tanaka", "Naito" };
名前をアルファベット順に並び替えたいのであれば次のように呼び出します。

int compare(const void* n1, const void* n2) { const char* str1 = (const char*)n1; const char* str2 = (const char*)n2; return strcmp(str1, str2); } void main(void) { char pName[][10] = { "Sato", "Suzuki", "Takahashi", "Tanaka", "Naito" }; int num = _countof(pName); int len = sizeof(pName[0]); qsort(pName, num, len, compare); for (int i = 0; i < _countof(pName); ++i) { printf("%s\n", pName[i]); } }
strcmp は2つの文字列を比較して +1, 0, -1 を返却する、まさに qsort で使うためにあるような関数です。このプログラムの実行結果は以下のようになります。
qsort実行結果
見事 ABC 順に並んでいますよね。でも、並び替えって何もアルファベット順ばかりとは限りません。例えば、文字数の短い順で並び替えしたい事だってあるかもしれません。その場合はどうすればよいのでしょうか。
文字数で並び替え
そうです、比較するために呼び出す関数の動作を変更すればよいのですね。文字数の短い順を実現するためには compare 関数を次のように書き換えます。

int compare(const void* n1, const void* n2) { const char* str1 = (const char*)n1; const char* str2 = (const char*)n2; return strlen(str1) - strlen(str2); }
strlen は文字列の長さを調べる関数ですので、その値を引いた結果を戻すだけで、文字数の少ない順の並び替えとなります。

文字数が同じ Tanaka と Suzuki は順不同(おそらく後ろの名前ほど最初に並び替わる)となります。折角なので、文字数が同じであれば、ABC 順に並び替えましょうか。

int compare(const void* n1, const void* n2) { const char* str1 = (const char*)n1; const char* str2 = (const char*)n2; int diff = strlen(str1) - strlen(str2); return diff == 0 ? strcmp(str1, str2) : diff; }
文字数とABC順の組み合わせ
このように並び方の判定を自由自在に変更することが出来ます。この compare こそがコールバック関数です。自分でコールバック関数の仕組みを実装する事は少ないかもしれませんが、動作原理は理解しておくと、何かの時にとても便利に使用することが出来ます。

※ 空調服専用のファンとバッテリーです。これがまた信じられないぐらい高いのですが、バッテリーってあまりに周囲が暑いとまともに能力を発揮しないので、専用で設計されたこちらを選ぶべきだと考えます。安物買って動かないとか、ファンがすぐに止まるだとか、最悪発火事故とかになったら目も当てられませんからねぇ。