指向成员函数的指针
声明一个指向成员函数的指针的语法示例:
float // 返回值类型
(Point::* // 指向类的成员函数
pmf) // 给指针起名
(); // 参数列表
看个例子:
class Point{
public:
float x() { printf("aaaa");};
float y() {};
protected:
float _x{}, _y{};
};
Point orgin;
Point *ptr = new Point;
- 定义一个指向成员函数的指针
float (Point::* pmf)(); // 声明
pmf = &Point::x; // 赋值
float (Point::* coord)() = &Point::x; //定义
- 使用指向成员函数的指针
(orgin.*pmf)();
(ptr->*coord)();
- 这些操作会被编译器转换为:
(pmf)(&orgin);
(coord)(ptr);
指向成员函数的指针的声明语法,其作用是为this指针的空间保留着。这也就是为什么静态成员函数(没有this)指针的类型是函数指针,而不是指向成员函数的指针的原因。
使用一个成员函数指针,如果并不用于虚函数、多重继承、虚基类等情况的话,并不会比使用一个非成员函数指针的成本更高。
- 取一个非静态成员数据的地址,得到的是该成员在类布局中的bytes位置。可以想象它是一个不完整的值,需要绑定在某个类对象的地址上,才能够被存取
- 取一个非静态成员函数的地址,如果该函数时非虚拟(
novirtual
),则得到的结果是它在内存中真正的地址。然而这个值也是不完全的,它也需要被绑定在某个类对象上,才能够通过它调用该函数。 - 所有的非静态成员都需要对象的地址(以参数this指出)
printf("%p\n", &Point::x); // 0x40082c
printf("%p\n", &Point::y); // 0x40084a
printf("%p\n", pmf); // 0x40082c
指向虚成员函数的指针
对于类:
class Point{
public:
virtual ~Point(){};
float x();
float y();
virtual float z();
protected:
float _x{}, _y{};
};
取z()的地址:
printf("%p\n", &Point::z);
得到的结果是0x11.通过pmf来调用z(),会被内部转换为一个编译时期的式子,一般形式如下:
(*ptr->vptr[(int)pmf])(ptr);
对一个指向成员函数的指针评估求值,会因为该值有两种意义而复杂化,其调用也有别与常规超过。 pmf的内部定义,也就是:
float (Point::* pmf)();
必须允许该函数能够寻址出nonvirtual x()和virtual z()两个成员函数,而这两个成员都这相同的原型:
//两者都可以被指定给pmf;
float Point::x(){return _x;};
float Point::z(){return 0;}
只不过一个代表内存地址,一个代表虚函数表中的索引值。因此,编译器必须定义pmf使它能够:
- 含有两种数值
- 更重要的是其数值可以被区别代表内存地址或者虚函数表中的索引值。
在cfront2.0非正式版中,这两个值可以被内含在一个普通指针内。使用了如下技巧区别该值是索引还是内存值:
(((int)pmf) & ~ 127)
? // non-virtual invocation
(*pmf)(ptr)
: // virtual invocation
(*ptr->vptr[(int)pmf])(ptr);
当然,这种实现技巧必须假设继承体系中最多只有128个虚函数,这并不是我们所希望的,这却证明是可行的。然而,多重继承的引入,导致需要更一般的实现方式,并趁机除去对虚函数的数目限制。
多重继承下,指向成员函数的指针
为了让成员函数的指针也能够支持多重继承和虚拟继承,有如下结构体:
// 一般结构,用于支持在多重继承下的指向成员函数的指针
struct __mptr{
int delta;
int index; // 虚函数表索引
union{
ptrtofunc faddr; // 非虚成员函数地址
int v_offset;
};
};
当index和faddr分别带有虚函数表索引和非虚成员函数地址(当index不指向虚函数表时,会被设为-1)。 此时,当:
(pmf->index < 0)
? // non-virtual invocation
(*pmf.faddr)(ptr)
: // virtual invocation
(*ptr->vptr[pmf.index])(ptr);
缺点在于:每一个调用操作都需要付出上诉成员,检查其是否为virtual或者nonvirtual。