指向类成员函数的指针并非指针

时间:2021-07-08 19:06:29

参考<<C++必知必会>>的相关章节
"指向类成员函数的指针",这个术语中包含了"类成员函数"的术语,但是严格的说,这里的成员函数只是指非静态成员函数,这个术语中还包含了"指针"这个术语,
但是严格的说,它即不包含地址,行为也不象指针,说得干脆点,那就是"指向类成员函数的指针"并非指针.尽管这个术语有很大的迷惑性,但是就其含义来说,
可以把一组同类型的函数抽象为一个"指向函数的指针",同样的道理,可以把一组类中同类型的类成员函数抽象为一个"指向类成员函数的指针",两者是一致的
"指向类成员函数的指针"和"指向函数的指针"有什么区别?从字面上就可以清楚的知道,前者是和类,对象相关的,而后者直接指向函数的地址

我们首先复习一下"指向函数的指针"如何使用?
void print()
{
}

 void (*pfun)(); //声明一个指向函数的指针,函数的参数是void,函数的返回值是void
 pfun = print;  //赋值一个指向函数的指针
 (*pfun)();   //使用一个指向函数的指针

比较简单,不是吗?为什么*pfun需要用()扩起来呢?因为*的运算符优先级比()低,如果不用()就成了*(pfun())

"指向类成员函数的指针"比"指向函数的指针"就多了个类的区别:

struct CPoint
{
 void plus(double x_, double y_)
 {
 }
 void minus(double x_, double y_)
 {
 }
 void mul(double x_, double y_)
 {
 }
 void dev(double x_, double y_)
 {
 }

 virtual void move(double x_, double y_)
 {

 }
 double x;
 double y;
};

void Oper(CPoint* pPoint, void (CPoint::*pfun)(double x_, double y_), int x, int y)
{
 (pPoint->*pfun)(x, y);
}

struct CPoint3d : public CPoint
{
 void move(double x_, double y_)
 {

 }
};

int main(int argc, char* argv[])
{
 CPoint pt;
 void (CPoint::*pfun)(double x_, double y_);
 int offset = 0;

 pfun = &CPoint::plus;
 offset = (int&)pfun;
 (pt.*pfun)(10, 10);
 Oper(&pt, pfun, 10, 10);

 pfun = &CPoint::minus;
 offset = (int&)pfun;
 (pt.*pfun)(10, 10);
 Oper(&pt, pfun, 10, 10);
 
 pfun = &CPoint::move;
 offset = (int&)pfun;
 (pt.*pfun)(10, 10);
 Oper(&pt, pfun, 10, 10);

 CPoint3d pt3d;
 void (CPoint3d::*p3dfun)(double x_, double y_);
 p3dfun = &CPoint3d::move;
 (pt3d.*p3dfun)(10, 10);

 //p3dfun = pfun; //正确
 //pfun = p3dfun; //错误
 pfun = (void (CPoint::*)(double, double))p3dfun;

 Oper(&pt3d, (void (CPoint::*)(double, double))p3dfun, 10, 10);
 
 return 0;
}

 void (CPoint::*pfun)(double x_, double y_);
这里是"指向类成员函数的指针"的声明,就是多了CPoint::的限定
 pfun = &CPoint::plus;
这里是"指向类成员函数的指针"的赋值,在赋值的时候必须用这种静态的方式
 (pt.*pfun)(10, 10);
这里是"指向类成员函数的指针"的使用,记住,解引用必须有实际的this指针地址,因此必须用有地址的对象pt来解引用,.*的语法有些怪异,不过我宁愿把它拆解为pt.和*pfun两部分来理解
 offset = (int&)pfun;
这里offset=4198410,当然不同的项目,不同的编译器这个值是不同的,由此也可以知道,"指向类成员函数的指针"确实是一个指针,其实由C++对象模型我们就应该知道这个结论了
,在C++对象模型中,成员函数是全局的,并不属于对象
有人想用这个值吗?或许可以用下面的代码:
 void (CPoint::*pfun2)(double x_, double y_);
 memcpy(&pfun2, &offset, sizeof(int));
 Oper(&pt, pfun2, 10, 10);
不过,我还是忍不住奉劝各位,尽量不要直接使用这个值,这毕竟是编译器内部实现的细节,实在有太多的人喜欢这种黑客似的代码并四处炫耀,真正的"指向类成员函数的指针"
的用法只应该包括声明,赋值和解引用

 pfun = &CPoint::move;
注意到这里的move是虚函数,那么这里还支持虚函数的多态吗?没有问题,"指向类成员函数的指针"支持多态,当然了,代价是,这时候这个指针就必须扩展为一个结构了,C++为了
"指向类成员函数的指针"支持多态是要付出代价的

 p3dfun = pfun; //正确
存在基类的"指向类成员函数的指针"到派生类的"指向类成员函数的指针"的隐式转换,其含义无疑是说基类的成员函数布局信息只是派生类中成员函数布局信息的一个子集,
因此这样的转换应该是没有问题的,但是反过来呢?
 //pfun = p3dfun; //错误
不存在派生类的"指向类成员函数的指针"到基类的"指向类成员函数的指针"的隐式转换,因为派生类中的成员函数并不一定能够在基类中找到
"指向类成员函数的指针"基类和派生类的关系和"指向类对象的指针"基类和派生类的关系完全相反,就"指向类成员函数的指针"的本质来说,这是合理的,但是这样的话,
我们就无法利用公共的Oper函数了,除非...

 pfun = (void (CPoint::*)(double, double))p3dfun; //强制转换
我们做强制转换是可以的
 Oper(&pt3d, (void (CPoint::*)(double, double))p3dfun, 10, 10);
而且也只有强制转换才可以利用公共的Oper函数了,这里的Oper调用的是pt3d中的move函数,没有错误的
但是是否一定要这样做呢?这取决于程序员自己的选择