3.6 指向 data members 的指针

指向 data member 的指针是一个颇有用处的语言特性, 特别是如果需要详细调查 class members 的底层布局的话。这个调查可以帮助你决定 vptr 是放在尾端还是起始处。 另一个用途是可以用来决定 clas 中 access sections 的次序。
其中有一个 virtual function, 一个 static data member, 以及三个坐标值:

class Point3d
{
public:
    virtual ~Point3d();
    //...
protected:
    static Point3d origin;
    float _x, _y, _z;
};

每一个 Point3d class object 含有三个坐标值,以及一个 vptr,static data member origin 被放在 class object 之外。唯一可能因编译器不同而不同的是 vptr 的位置。C++ standard 允许 vptr 被放在对象中的任何位置,但是所有编译器不是把 vptr 放在头部就是把它放在尾部。
取某个坐标成员的地址, 代表什么意思?

&Point3d::z;	// 得到类对象种的偏移位置

如何区分一个没有指向任何 data member 的指针和一个指向第一个 data member 的指针?

float Point3d::*p1 = 0;
float Point3d::*p2 = &Point3d::_x;

p1 == p2? 为了区分 p1 与 p2 每一个真正的 member offset 的值都被加上 1,因此不论编译器或使用这都必须记住,在真正使用该值以指出一个 member 之前,请先减掉 1。
另外, 理解 指向 data member 的指针后, 我们就会发现要解释:

&Point3d::_z;	// 取一个 nonstatic data member 的地址,得到它在 class 中的 offset
&origin._z;	//取一个绑定于 class object 的 data member 的地址得到该 member 在内存中的真正地址

把 &origin.z 所得结果减去 _z 的偏移值,并加 1, 就会得到 origin 的起始地址。 上一行的返回值的类型应该是 float* 而不是 float Point3d:😗 由于上述操作所参考的是一个特定实例, 所以取一个 static data member 的地址, 意义也相同。

在多重继承之下,若要将第二个(或后继) base class 的指针和一个与 derived 绑定的 member 结合起来, 那么将会因为需要加入 offset 值而变得复杂:

struct Base1{ int val1; }
struct Base2{ int val2; }
struct Derived:Base1, Base2{...};

void func1(int Derived::*dmp, Derived *pd)
{
    // 期望第一个参数是 指向 derived class 的 member 的指针
    // 但假如传进的是一个指向 base class 的 member 的指针, 会怎样?
    pd->*dmp;
}

void func2(Derived *pd)
{
    //bmp 将成为 1
    int Base2::*bmp = &Base2::val2;	// offset, bmp == 1

    //但是在 Derived 中, val2 的offset== 5
    func1(bmp, pd);
}

当 bmp 被作为 func1() 的第一个参数时, 它的值就必须因介入的 Base1 class 的大小调整, 否则 Func1 中这样的操作:
pd->*dmp;
将存取到 Base1::val1, 而非程序员所以为的 Base2::val2。要解决这个问题,必须:
//编译器进行的内部转换

Func1(bmp + sizeof(Base1), pd);
//防范措施, 防止bmp == 0
Func1(bmp ? bmp + sizeof(Base1) : 0, pd);

“指向 Members 的指针”的效率问题

在不优化的前提下,效率: 直接存取 > 已绑定成员存取 > 未绑定对象指针存取?

当考虑继承时, 一般的继承并不影响代码的效率, 但是如果是虚拟继承, 那么因为每一层虚拟继承都导入一个额外层次的间接性

posted @ 2021-04-20 10:30  点|滴  阅读(9)  评论(0)    收藏  举报