3.6 指向Data Members的指针(Pointer to Data Members)
指向data members的指针,是一个有点神秘但颇有用处的语言特性,特别是如果需要详细调查 class members的底层布局的话,这样的调用可以决定vptr是放在 class 的起始位置或是尾端.另一个用途,可用来决定 class 中的access sections的次序.考虑下面的Point3d声明,其中有一个 virtual function,一个 static data member,以及三个坐标值:
class Point3d {
public:
virtual ~Point3d();
protected:
static Point3d origin;
float x, y, z;
};
每一个Point3d class object含有三个坐标值,依次为x,y,z,以及一个vptr.至于 static data member origin,将被放在 class object之外,唯一可能因编译器不同而不同的是vptr的位置.C++ Standard允许vptr被放在对象中的任何位置:在起始位置,在尾端,或是在members之间.然而实际上,所欲编译器不是把vptr放在对象的头部,就是放在对象的尾部.
那么,取某个坐标成员的地址,代表什么意思?例如,以下操作所得到的值代表什么:
& Point3d::z;
上述操作将
得到z坐标在 class object中的偏移量(offset),最低限度其值将是x和y的大小总和,因为C++语言要求同一个access level中的members的排列次序应该和声明次序相同.
(因为是2000年写的书,编译器比较早, 有点疑惑)现在在g++4.8上如下打印地址:
cout << "&Point3d::_x = " << &Point3d::_x << endl;
cout << "&Point3d::_y = " << &Point3d::_y << endl;
cout << "&Point3d::_z = " << &Point3d::_z << endl;
cout << "sizeof(Point3d) = " << sizeof(Point3d) << endl;
得到如下结果:
如何区分一个"没有指向任何data member"的指针和一个指向"第一个data member"的指针?考虑这样的例子:
float Point3d::*p1 = 0;
float Point3d::*p2 = &Point3d::x;
if (p1 == p2) {
cout << "p1 & p2 contain the same value --";
cout << "they must address the same member!" << endl;
}
为了区分p1和p2,每一个真正的member offset值都被加1.因此,不论编译器或使用者都必须记住,在真正使用该值以指出一个member之前,请先减掉1.
认识"指向data members的指针"之后,要解释:
&Point3d::z;
&origin.z;
之间的差异,就非常明确了.鉴于
"取一个nonstatic data member的地址,将会得到它在class中的offset",取一个"绑定于真正class object上的data member"的地址,将会得到该member在内存中的真正地址.把
&orgin.z
所得结果减z的偏移值(相当于origin起始地址),并加1,就会得到origin起始地址.上一行的返回值类型应该是:
float *
而不是
float Point3d::*
结果如下所示:
"指向Members的指针"的效率问题
下面的测试,了解使用"指向members的指针"所带来的影响,第一个例子是要取得一个"已绑定的member"的地址:float *ax = *pA._x;
然后进行赋值操作,如下:
*bx = *ax - *bz;
*by = *ay + *bx;
*bz = *az + *by;
第二个例子则是针对三个members,取得"指向data member的指针"的地址:
float Point3d::*ax = &Point3d::x;
而赋值操作,都是使用"指向data member的指针"语法,把数值绑定到对象pA和pB中:
pB.*bx = pA.*ax - pB.*bz;
pB.*by = pA.*ay + pB.*bx;
pB.*bz = pA.*az + pB.*by;
测试结果如下所示:
....(与之前没有太大差别)