C++对象模型——Data Member的绑定(第三章)

来源:互联网 发布:阿里云服务器初始密码 编辑:程序博客网 时间:2024/05/21 18:30

3.1    Data Member的绑定 (The Binding of a Data Member)

    考虑下面这段代码:
// 某个foo.h头文件,从某处含入extern float x;// 程序员的Point3d.h文件class Point3d {public:    Point3d(float, float, float);    // 问题:被传回和被设定的x是哪一个x?    float X() const { return x; }    void X(float new_x) const { x = new_x; }private:    float x, y, z;};
    Point3d::X()传回哪一个x?是 class 内部的那个x,还是外部(extern)的那个x?现在的答案是内部的.但过去的答案并不是这样.
    在C++最早的编译器上,如果在Point3d::X()的两个函数实例中对x进行参阅(引用)操作,这操作将会指向global x object.这样的绑定结果几乎普遍存在,并因此导出早期C++的两种防御性程序设计风格:
    1.    把所有的data members放在 class 声明开始处,以确保正确的绑定:
class Pointed{    // 防御性程序设计风格 #1    // 在class声明开始先放置所有的data members    float x, y, z;public:    float X() const { return x; }    // ...etc...};
    2.    把所有的inline functions,不管大小都放在 class 声明之外:
class Point3d{public:    // 防御性程序设计风格 #2    // 把所有的inlines都移到class之外    Pointed();    float X() const;    void X(float) const;    // ...etc...};inline float POint3d::X() const {    return x;}
    这些程序设计风格事实上到今天还存在,这个古老的语言规则被称为"member rewriting rule",大意是"一个inline函数实体,在整个class声明未被完全看见之前,是不会被评估求值(evaluated)的".C++ Standard以"member scope resolution rules"来精炼这个"rewriting rule",其效果是,如果一个inline函数在 class 声明后立刻被定义的话,那么就还是会对其评估求值(evaluated).也就是说,如果写下面的代码:
extern int x;class Point3d {public:    // 对于函数本身的分析将延迟直至class声明的右括号出现才开始    float X()const { return x; }private:    float x;};
    对于member functions本身的分析,会直到整个 class 的声明都出现了才开始.因此,在一个 inline member function内的一个data member绑定操作,会在整个 class 声明完成后才发生.

3.2    Data Member的布局 (Data Member Layout)

    已知下面一组data members:
class Point3d {public:    // ...private:    float x;    static List<Point3d *> *freeList;    float y;    static const int chunkSize = 250;    float z;};
    Nonstatic data members在 class object中的排列顺序将和其被声明的顺序一样,任何中间介入的 static data members如freeList和chunkSize都不会被放进对象布局中.在上述例子中,每一个Point3d对象是由三个 float 组成,次序是x,y,z.static data members存放在程序的data segment中,和个别的 class objects无关.
    C++ Standard要求,在同一个access section(也就是 private,public,protected 等区段)中,members的排列只需符合"较晚出现的members在class object中有较高的地址"这一条件即可.也就是说,各个members并不一定得连续排列,什么东西可能介入声明的members之间呢?members的边界调整(alignment)可能就需要填充一些bytes.对于C和C++而言,这的确是真的,对当前的C++编译器实现情况而言,这也是真的.
    编译器还可能合成一些内部使用的data members,以支持整个对象模型,vptr就是这样的东西,当前所有的编译器都把它插入在每一个"内含virtual function之class"的object,vptr会放在什么位置呢?传统上它被放在所有明确声明的members的最后,不过如今也有一些编译器把vptr放在一个 class object的最前端.C++ Standard对布局持放任态度,允许编译器把那些内部产生出来的members自由放在任何位置上,甚至放在那些被程序员声明出来的members之间.
    C++ Standard也允许编译器将多个access sections中的data members自由排列,不必在乎它们出现中在 class 声明中的次序,也就是说,下面这样的声明:
class Point3d {public:    // ...private:    float x;    static List<Point3d *> *freeList;private:    float y;    static const int chunkSize = 250;private:    float z;};
    其 class object的大小和组成都和先前声明的那个相同,但是members的排列次序则视编译器而定.
    当前编译器都是把一个以上的access sections连锁在一起,依照声明的次序成为一个连续区块.Access sections的多少并不会导致额外负担,例如在一个section中声明8个members,或者在8个sections中总共声明8个members,得到的object大小是一样的.
    下面这个 template function,接受两个data members,然后判断谁先出现在 class object中.如果两个members都是不同的access sections中的第一个被声明者,此函数可以用来判断哪一个section先出现:
template <class class_type1, class class_type2, class class_type3>char * access_order(data_type1 class_type::*mem1, data_type2 class_type::*mem2) {    assert(mem1 != mem2);    return mem1 < mem2 ? "member 1 occurs first" : "member 2 occurs first";}
    上述函数可以这样被调用:
access_order(&Point3d::z, &Point3d::y);
    于是class_type会被绑定为Point3d,而data_type1和data_type2会被绑定为float.


0 0
原创粉丝点击