double // return type (Point::* // class the function is member pmf) // name of the pointer to member (); // argument list然后可以这样定义并初始化该指针:
double (Point::*coord)() = &Point::x;也可以这样指定值:
coord = &Point::y;想调用它,可以这样做:
(origin.*coord)();或
(ptr->*coord)();这些操作会被编译器转化为:
(coord)(&origin);和
(coord)(ptr);指向member function的指针的声明语法,以及指向"member selection运算符"的指针,其作用是作为 this 指针的空间保留者.这这也就是为什么 static member function(没有 this 指针)的类型是"函数指针",而不是"指向member function的指针"的原因.
float (Point::*pmf)() = &Point::z; Point *ptr = new Point3d;pmf,一个指向member function的指针,被设值为Point::z()(一个 virtual function)的地址,ptr则被指定以一个Point3d对象,如果直接经由ptr调用z():
ptr->z();则被调用的是point3d::z(),但如果从pmf间接调用z()呢?
(ptr->pmf)();仍然是Point3d::z()被调用吗?也就是说,虚拟机制仍然能够在使用"指向member function的指针"的情况下运行吗?答案是yes,问题是如何实现呢?
class Point { public: virtual ~Point(); float x(); float y(); virtual float z(); };然而取得destructor的地址:
&Point::~Point;得到的结果是1,取x()或y()的地址:
&Point::x(); &Point::y();得到的则是函数在内存中的地址,因为它们不是 virtual,取z()的地址:
&Point::z();得到的结果是2,通过pmf来调用z(),会被内部转化为一个编译时期的式子,一般形式如下:
(*ptr->vptr[(int)pmf])(ptr);对一个"指向member function的指针"评估求值(evaluted),会因为该值有两种意义而复杂化;其调用操作也将有别于常规调用操作.pmf的内部定义,也就是:
float (Point::*pmf)();必须允许该函数能够寻址出nonvirtual x()和 virtual z()两个member functions,而那两个函数有着相同的原型:
// 二者都可以被指定给pmf float Point::x() { return _x; } float Point::z() { return 0; }只不过其中一个代表内存地址,另一个代表 virtual table中的索引值.因此,编译器必须定义pmf使它能够(1)还有两种数值,(2)更重要的是其数值可以被区别代表内存地址还是 virtual table中的索引值.
// 一般结构,用以支持在多重继承下指向member functions的指针 struct __mptr { int delta; int index; union { ptrtofunc faddr; int v_offset; }; };它们要表现什么呢?index和faddr分别(不同时)带有 virtual table索引和nonvirtual member function地址.在该模型下,像这样的调用操作:
(ptr->*pmf)();会变成:
(pmf.index < 0) ? // non-virtual invocation (*pmf.faddr)(ptr) : // virtual invocation (*ptr->vptr[pmf.index](ptr));这种方法所受到的批评是,每一个调用操作都得付出上述成本,检查其是否为 virtual 或 nonvirtual.Microsoft把这项检查拿掉,导入一个它所谓的vcall thunk.在此策略下,faddr被指定的要不就是真正的member function地址(如果函数是nonvirtual的话),要不就是vcall thunk的地址.于是 virtual 或novirtual函数调用操作透明化,vcall thunk会选出并调用相关 virtual table 中的适当slot.
extern Point3d foo(const Point3ed&, Point3d(Point3d::*)()); void bar(const Point3d& p) { Point3d pt = foo(p, &Point3d::normal); }其中&Point3d::normal的值类似这样:
{0, -1, 10727417}将需要产生一个临时性对象,有明确的初值:
__mptr temp = {0, -1, 10727417} foo(p, temp);本节一开始的那个结构体,delta字段表示 this 指针的offset值.而v_offset字段放的是一个 virtual(或多重继承中的第二或后继的)base class 的vptr位置.如果vptr被编译器放在 class 对象的起始处,这个字段就没有必要了,代价则是C对象兼容性降低.这些字段只在多重继承或虚拟继承的情况下才有其必要性,有许多编译器在自身内部根据不同的 class 特性提供多种指向member functions的指针形式,例如Microsoft就供应了三种风格:
Point3d* (*pf)(const Point3d&, const Point3d &) = cross_product; for (int iters = 0; iters < 10000000; iters++) (*pf)(pA, pB); return 0;第二个测试(指向 member function 的指针)的声明和调用操作如下:
Point3d* (Point3d::*pmf)(const Point3d &) const = &Point3d::cross_product; for (int iters = 0; iters < 10000000; iters++) (pA.*pmf)(pB);上述操作会被转化为以下的内部形式,于是以下的函数调用:
(pA.*pmf)(pB);会被转化为这样的判断:
pmf.index < 0 ? (*pmf.faddr)(&pA + pmf.delta, pB) : (*pA.__vptr__Point3d[pmf.index].faddr) (&pA + pA.__vptr__Point3d[pmf.index].delta, pB);一个"指向member function的指针"是一个结构,内含三个字段:index,faddr和delta.index若不是内带一个相关 virtual table的索引值,就是以-1表示函数是 nonvirtual.faddr带有nonvirtual member function的地址.delta带有一个可能的 this 指针调整.
版权声明:本文为博主原创文章,未经博主允许不得转载。
C++对象模型——指向Member Function的指针 (Pointer-to-Member Functions)(第四章)
原文:http://blog.csdn.net/yiranant/article/details/47424267