はじめに関数ポインタについてお話しします。
概念的には、関数ポインタとは、言うなればある関数の分身のことです。
変数を指すポインタがその変数の分身であることと同様です。
関数ポインタの実体は、変数を指すポインタと同様、メモリ番地(つまりは単なる数値)です。
変数の目的は、何らかの値を保存しておき、必要なときに読み書きすることですから、その分身たるポインタでもその変数を読み書きできます。
また、分身ですから(関数から抜け出すなどして)その変数が無効になった場合、その変数を指すポインタも同時に無効になります。
一方で関数の目的は、呼び出されることです。
ですから、その分身たる関数ポインタでもその関数を呼び出せます。
また、(通常は考えにくいことですが)何らかの理由でその関数が無効になった場合も、やはりそのポインタも同時に無効になります。
ActiveBasic での関数ポインタの使い方は、ここでは省略しますね。
ActiveBasic に関数ポインタが導入されたのがどうやら 2005 年頃のようで、フォーラムの過去ログにも関数ポインタに関する話が埋もれていると思うので。
次に Select Case についてお話しします。
ActiveBasic の Select Case 構文でどのようなコードが生成されるのかは把握していませんが、一般的な Select Case の実装法としては、次のようなコードの等価変形が考えられます。
コード:
If PtnNo = 0 Then
Ptn00()
Else If PtnNo = 1 Then
Ptn01()
Else If PtnNo = 2 Then
Ptn02()
(中略)
Else If PtnNo = 62 Then
Ptn62()
Else If PtnNo = 63 Then
Ptn63()
End If
このコードだと PtnNo の値が小さければわりと早く目的の関数を呼び出せますが、PtnNo の値が大きめになってくると、いつまでも Else If 〜 Else If 〜 の評価を繰り返し続けるので、少々時間がかかります。
具体的には、最悪の場合で PtnNo = 63 のとき、比較処理も 63 回行っていずれの比較も一致せず、最後のブロックの PtnNo = 63 の評価でようやく処理が決定する、ということになります。
(厳密なことをいえば、本当の「最悪の場合」は PtnNo = 63 ではなく、PtnNo の値が想定される値の範囲外だった場合なのですが。)
この変形はあくまで一般的な解法で、トモカズさんのコードの場合、既にお察しの通り Select Case のもっと効率的な等価変形ができる特殊ケースに該当します。
それは次のようなコードになります。
コード:
' pPtn は関数ポインタ型の配列
If 0 <= PtnNo And PtnNo <= 63 Then ' PtnNo の値の範囲チェック:重要!
pPtn[PtnNo]()
End If
PtnNo の値が 0 〜 63 の範囲で飛び番がなければ、これで OK です。
ただし、pPtn の各要素にはあらかじめ適切なポインタが格納されていることが前提になります。
なお、PtnNo の値が「絶対に」0 〜 63 の範囲内に収まっているという確信があれば If 文は消してしまってもいいでしょう。
(それでもデバッグ用に残しておいたほうが賢明というか、その代わりに assert を入れておくべきでしょうが、ActiveBasic にアサーションってありましたっけ?)
一応 pPtn 配列の初期化コードも、そのまま流用できる形で載せておきます。
コード:
Dim pPtn As *Sub()
pPtn[0] = AddressOf(Ptn00)
pPtn[1] = AddressOf(Ptn01)
(中略)
pPtn[63] = AddressOf(Ptn63)
注意点があります。
配列の初期化には、前出の If 〜 Else If 〜 Else If 〜 と同等程度の手間がかかるということを忘れないでください。
ですから、たとえばループの中に初期化処理を書いて、初期化も一緒に繰り返すことの無いように。
pPtn をグローバル変数にして、プログラムの起動時に一度だけ初期化するのがお勧めです。
この手法は、ジャンプテーブルなどと呼ばれる古典的な Select Case の最適化技法です。
配列を移動先(ジャンプ先)が記してある表(テーブル)に見立ててこう呼びます。
#長ったらしい文章でごめんなさいね。
#僕の書く文章ってどうしてこう、いつももっさりしてしまうのだろう。。。