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

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.

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-12-20 23:38:53

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

C++对象模型——Data Member的存取(第三章)

3.3    Data Member的存取 已知下面这段代码: Point3d origin; origin.x = 0.0; x的存取成本是什么? 答案视x和Point3d如何声明而定,x可能是个 static member,也可能是个nonstatic member.Point3d可能是个独立(非派生)的 class,也可能从另一个单一的base class 派生而来;虽然可能性,但它甚至可能是从多重继承或虚拟继承而来.下面数节将依次检验每一种可能性. 先看这样一个问题,如果有两个定义,or

3.1 Data Member的绑定

文章开始提出了一段示例代码,并讨论了返回哪个x的问题.然后 a)   给出了我们普遍认为正确的回答,并肯定了这个想法: b)   提醒大家,这在以前的编译器实现中,并非正确. 在早期的实现中,首先绑定的是“全局变量x”.因为该class中的x的声明尚未可见.由此导致的就是“防御性编程风格”.总结起来,具体做法有两种. c)   将所有的data member的声明提前: d)   将内联函数的实现移至class的声明之外. 对于第一点,我们容易理解.因为变量的声明中,较晚的声明总是可以覆盖之前的

深度探索C++对象模型 第三章 Data 语意学

一个有趣的问题:下列 类 sizeof大小 class X{}    //1 class Y:public virtual X{} //4 or 8 class Z:public virtual X{} // 4 or 8 class A:public Y,public Z{} // 8 or 12 主要原因:为了保持每一个类生成对象在内存中的唯一性,编译器必须要给空类生成一个char来维持object的唯一性: 而virtual继承中,仅保持了base class的指针,有些编译器会继承bas

第三章、Data语意学

无虚继承的空类占一个字节,用于标记该类.有虚继承的空类至少占4个字节(可能继承的空类占很大空间).对齐情况class X{float i;//8char j;//1int k;//4double b;//下面重新的字节8,上面用来对齐};sizeof(X)=24 class Y{char j;//1int k;//4};sizeof(Y)=8 class Z{char j;//1int k;//4double b;};sizeof(Y)=16 1.Data Member的绑定有两种情况:情况一:

C++对象模型——&quot;继承&quot;与Data Member(第三章) .

3.4 "继承"与Data Member 在C++继承模型中,一个derived class object所表现出来的东西,是其自己的members加上其base class members的总和.至于derived class members和base class members的排列次序并未在C++ Standard中强制指定:理论上编译器可以自由安排.在大部分编译器上,base class members总是先出现,但属于 virtual base class的除外. 了解这种继

C++对象模型——指向Member Function的指针 (Pointer-to-Member Functions)(第四章)

4.4 指向Member Function的指针 (Pointer-to-Member Functions) 取一个nonstatic data member的地址,得到的结果是该member在 class 布局中的byte位置(再加1),它是一个不完整的值,须要被绑定于某个 class object的地址上,才可以被存取. 取一个nonstatic member function的地址,假设该函数是nonvirtual,则得到的结果是它在内存中真正的地址.然而这个值也是不全然的,它也须要被绑定

指向 Data Member 的指针及相关的效率影响

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

Data 语意学---Data member的存取效率

<深度探索C++对象模型> 对于data member来说,有两种情况 static data member数据 每一个static data member只有一个实体,存放在程序的data segment之中,无论以何种方式,无论类的继承关系如何复杂,存取路径都是非常直接 Nonstatic data members 直接存放在一个class object之中,是属于一个对象的,是需要一个叫做偏移量的值来索引的. 尤其是虚拟继承,虚拟继承将为"经由base class subobj

第三章:绑定语法(1)

第三章所有代码都需要启用KO的 ko.applyBindings(viewModel); 功能,才能使代码生效,为了节约篇幅,所有例子均省略了此行代码. 1 visible 绑定 目的 visible绑定到DOM元素上,使得该元素的hidden或visible状态取决于绑定的值. 例子 <div data-bind="visible: shouldShowMessage"> You will see this message only when "shouldSh