羅塞達碑石

C++的 Function Pointer 使用問題!

侯捷 1998.12.04


最近網路的討論群上出現 C++的 Function Pointer 使用問題 這個題目。

此題頗為有趣。Frank 網友的疑惑,經過前面數位網友的討論,我們發現,"強制轉型" 表面上可以解決問題,但骨子裡問題還是存在如下:

若以 function pointer 做為 class 的一筆 data member,而我們希望把 "argument list 不同" 的各個函式的指標塞給它(指定給上述的 data member),那麼我們該如何解決 "argument list 不同" 的問題?

這很像 MFC 裡頭的 message map。我想 MFC 的做法很可供參考。如果各位有 <深入淺出 MFC> 2/e 的話,可看 p580「羅塞達石碑:AfxSig_xx 的奧秘」一節。提要如下:

MFC 設計了一個 struct(C++ 的 struct 與 class 有相同的能力):

struct AFX_MSGMAP_ENTRY
{
UINT nMessage; // <--
UINT nCode;
UINT nID;
UINT nLastID;
UINT nSig; // <-- enum AfxSig
,例如 AfxSig_vwl, AfxSig_iis, ...
AFX_PMSG pfn; // <--
}


其中的 nMessage 和 pfn 就是 Frank 網友想要的東西。只不過在第一個欄位上,Frank 網友是以 string 表示,此處則以 UINT 表示。

pfn 的型別(AFX_PMSG)定義如下:

typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);

顯然 AFX_PMSG 是 pointer of CCmdTarget's member function. 上述 typedef 將 AFX_PMSG 定義為無參數(void)函式的指標。

MFC 又定義了一個 enum AfxSig(《深入淺出 MFC》2/e p581)及一個 union MessageMapFunctions(《深入淺出 MFC》2/e p583),根據前述 nSig 欄位(內容是 Afx_xxx),將「無參數函式」的指標轉換為「有各種參數之函式」的指標。由於各式各樣的函式指標係定義於 union MessageMapFunctions 之中,都是 4 bytes,利用 union 的特性可順利做轉換,而又解決 "argument list 不同" 的問題。換句話說真正的函式指標只是 pfn,但通過 union,它展現了不同的形象。

註:羅塞達碑石(Rosetta Stone),1799 年拿破崙遠征埃及時,由一名官員在尼羅河口羅塞達發現,從此揭開古埃及象形文字之謎。石碑是黑色玄武岩,高 114 公分,厚 28 公分,寬 72 公分。經法國學者波倫研究後,世人因得順利研讀古埃及文獻。上述的 AfxSig_xxx 使我們揭開了 MFC 的 message map 之謎,因以喻之!



以下是我寫的一個小小模擬程式。<多型與虛擬> 改版時 (2/e),我會把這個題目加入第一章中。

#0001 // file : fptr2.cpp (simulate MFC message mapping)
#0002 // author : jjhou
#0003 // date : 1998.12.04
#0004 // build : cl -GX fptrs.cpp
#0005 // bcc32 fptrs.cpp
#0006
#0007 #include <iostream>
#0008 #include <string>
#0009
#0010 using namespace std;
#0011
#0012 // generic type of function pointer
#0013 typedef void (*PFUNC)(void);
#0014
#0015 // command ID definition
#0016 #define CMD_COPY 0
#0017 #define CMD_PASTE 1
#0018 #define CMD_CUT 2
#0019 #define CMD_NULL 0xFFFF
#0020
#0021 // command function implementation
#0022 int copy(void) { cout << "copy" << endl; return 0; }
#0023 bool paste(string& str) { cout << "paste: " << str << endl; return true; }
#0024 void cut(int i, int j) { cout << "cut: " << i << " " << j << endl; }
#0025
#0026 enum Sig // ref <Dissecting MFC 2/e> p581
#0027 {
#0028 Sig_end = 0, // [marks end of message map]
#0029
#0030 Sig_iv, // int (void)
#0031 Sig_bs, // bool (string&)
#0032 Sig_vii, // void (int, int)
#0033 };
#0034
#0035 union CmdFunctions // ref <Dissecting MFC 2/e> p583
#0036 {
#0037 PFUNC pfn; // generic function pointer
#0038
#0039 // specific type safe variants
#0040 int (*pfn_iv)(void);
#0041 bool (*pfn_bs)(string&);
#0042 void (*pfn_vii)(int, int);
#0043 };
#0044
#0045 struct Command // ref <Dissecting MFC 2/e> p580
#0046 {
#0047 unsigned int nCmd;
#0048 unsigned int nSig;
#0049 PFUNC pfn; // command handler
#0050 };
#0051
#0052 Command commands[] = // ref <Dissecting MFC 2/e> p555
#0053 {
#0054 { CMD_COPY, Sig_iv, (PFUNC)(int (*)(void))copy },
#0055 { CMD_PASTE, Sig_bs, (PFUNC)(bool (*)(string&))paste },
#0056 { CMD_CUT, Sig_vii, (PFUNC)(void (*)(int, int))cut },
#0057 { CMD_NULL, Sig_end, (PFUNC)0 }
#0058 };
#0059
#0060 void main(void)
#0061 {
#0062 unsigned int icmd;
#0063 int iparam1, iparam2;
#0064 string sparam;
#0065 union CmdFunctions cf; // ref <Dissecting MFC 2/e> p581
#0066
#0067 while (1) //
模擬不斷有 command 進來,不斷地處理之
#0068 {
#0069 //
備妥 command
#0070 cout << "Command (0 or 1 or 2, any other will exit) : ";
#0071 cin >> icmd;
#0072
#0073 //
備妥 parameters
#0074 switch (icmd) {
#0075 case 0 :
#0076 break;
#0077
#0078 case 1 :
#0079 cout << "param str = ";
#0080 cin >> sparam;
#0081 break;
#0082
#0083 case 2 :
#0084 cout << "iparam1 = ";
#0085 cin >> iparam1;
#0086 cout << "iparam2 = ";
#0087 cin >> iparam2;
#0088 break;
#0089
#0090 default :
#0091 cout << "command error!" << endl;
#0092 exit(1);
#0093 }
#0094
#0095 //
搜尋適當的 cmd index
#0096 //
本例中,icmd 即為 commands[] index.
#0097 //
其他情況可能未必
#0098 // ...
#0099
#0100 //
呼叫適當的 cmd handler ref <Dissecting MFC 2/e> p581
#0101 cf.pfn = commands[icmd].pfn;
#0102 switch (commands[icmd].nSig) {
#0103 case Sig_iv:
#0104 cout << "result= " << (*cf.pfn_iv)() << endl;
#0105 break;
#0106
#0107 case Sig_bs:
#0108 cout << "result= " << (*cf.pfn_bs)(sparam) << endl;
#0109 break;
#0110
#0111 case Sig_vii:
#0112 (*cf.pfn_vii)(iparam1, iparam2);
#0113 break;
#0114 }
#0115 } // while
#0116 }
#0117
#0118

#0119 /*
#0120 H:\g001p\prog\fptr.01>fptr2
#0121 Command (0 or 1 or 2, any other will exit) : 0
#0122 copy
#0123 result= 0
#0124 Command (0 or 1 or 2, any other will exit) : 1
#0125 param str = helloMFC
#0126 paste: helloMFC
#0127 result= 1
#0128 Command (0 or 1 or 2, any other will exit) : 2
#0129 iparam1 = 839
#0130 iparam2 = 912
#0131 cut: 839 912
#0132 Command (0 or 1 or 2, any other will exit) : 9
#0133 command error!
#0134 */


--- the end