在C++編程中,成員函數指標並不常用,並且由於它實現比較複雜,很容易導致編程錯誤。
基本用法:
class B {
public:
int f(int a, int b) {
return (a + b) * m_c;
}
int m_c;
};
int main() {
typedef int (B::*MEM_FUNC)(int, int); // 定義成員函數指標類型MEM_FUNC
MEM_FUNC pmf; // 定義成員函數指標pmf
pmf = NULL; // 指標可以設定為NULL
pmf = &B::f; // 指標可以指向一個成員函數
B b1, b2;
b1.m_c = 10;
b2.m_c = 20;
int i1 = (b1.*pmf)(30, 40); // 調用b1.f(30, 40)
int i2 = (b2.*pmf)(50, 60); // 調用b2.f(50, 60)
B* p = &b1;
int i3 = (p->*pmf)(70, 80); // 調用b1.f(70, 80)
return 0;
}
C++語言最初設計的時候,沒有繼承,沒有虛函數,等等,所以成員函數指標的使用也並不複雜。但是隨著多重繼承、虛擬繼承等特性被引入,成員函數指標開始變得複雜了。
首先是大小的改變。雖然“成員函數指標”是一種指標,但它所佔的空間卻可能比一般指標要大。以VC為例(VC6至VC2010均如此),在單繼承、多繼承、虛擬繼承的情況下,成員函數指標的大小分別為4位元組、8位元組、12位元組。如果不知道一個類是否繼承(此時這個類只有一份向前聲明,而沒有提供實際定義),則它的成員函數指標最多可以佔據16個位元組,大大的超過了一般指標的4位元組。
然後是類型轉換問題。一般情況下,衍生類別的指標轉化為基類的指標不會有任何問題(因為繼承就意味著is a kind of)。但是,衍生類別的成員函數指標卻不可以轉化為基類的成員函數指標。範例程式碼:
int main() {
typedef void(std::istream::*PMF_B)(int);
typedef void(std::ifstream::*PMF_D)(int);
PMF_B b = 0;
PMF_D d = 0;
d = b; // 編譯錯誤
return 0;
}
為什麼編譯錯誤呢?從位元組數來講,因為衍生類別的成員函數指標,它的位元組數可能比基類的成員函數指標更多。如果成功轉換,則會導致位元組數減少,某些有用的資訊可能就丟失了。但更重要的原因是,衍生類別的成員函數指標可以指向衍生類別的函數,而衍生類別的這些函數可能是基類所沒有的。此時,就算成功的完成轉換,在運行之時也肯定導致記憶體錯誤。反過來,如果把基類的成員函數指標轉換為衍生類別的成員函數指標,則是完全可以的。
還有就是關於VC系列編譯器,在一個問題上沒有按照C++標準來處理。標準規定各種成員函數指標之間,可以用reinterpret_cast來完成強制轉換,但VC系列編譯器在實現這一點時有存在問題,可能導致編譯錯誤。解決此問題的辦法是用union hack,具體作法是定義一個union,其中包含了轉換之前和轉換之後的成員指標類型。然後設定/取值即可。
最後,boost提供了一套mem_fn,它是std::mem_fun更加通用的版本(std::mem_fun只支援無參數、單參數版本,boost::mem_fn支援更多參數)。可以把對成員函數的調用轉化為類似普通函數的形式。這一套mem_fn已經被納入到TR1,在VC 2008 SP1和以上版本可以直接使用。