[转]使用成员函数指针的一些注意事项
在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和以上版本可以直接使用。