1.关于对象
关于封装后的布局成本:
就像struct一样data member直接包含在class object之中,而member function虽然在class的声明之内,却不会出现在object中。每一个noninline member function只会诞生一个函数实体。至于inline function则会在每一个使用者身上产生一个函数实体。
nonstatic member data + alignment + C++ 在布局和存取时间上主要的额外负担是由virtual引起的,包括:
1.virtual function机制用以支持一个有效率的“执行期绑定”
2.virtual base class用以实现“多次出现在继承体系中的base class,有一个单一而被共享的实体”
多态由指向派生类的基类指针或引用实现。也就是说:“指针类型”会教导编译器如何解释某个特定地址中的内存内容和大小。多态的主要用途是经由一个共享的接口来影响类型的封装,共享接口以虚函数机制引发,在执行期根据对象的真正类型解析出到底是哪一个函数实例被调用。
• 静态多态性通常称为编译时多态,到底模板是不是多态???我个人认为不是
• 动态多态性通常称为运行时多态,通过虚函数来实现
C++ADT程序风格,如今也被称为object base一种非多态的数据类型。
在C++对象中表现为中有三种模型(C++对象模型是在前两种对象模型上发展而来的,甚至于局部上直接用到前两种对象模型):
- 简单对象模型(指向成员的指针)、
- 表格驱动对象模型(以此模型作为支持虚函数的方案)、
- C++对象模型。
三种编程典范 - 程序模型:数据和函数分开。 - 抽象数据类型模型(OB):数据和函数一起封装以来提供。 - 面向对象模型(OO):可通过一个抽象的base class封装起来,用以提供共同接口,需要付出的就是额外的间接性。
转型(cast)其实是一种编译器指令。
- 大部分情况下它并不改变一个指针所含的真正地址,它只影响“被指出之内存大小和其内容”的解释方式。
- 如一个类型为void *的指针只能够持有一个地址,但不能 通过它操作所指object。
2.构造函数语意学
bitwise copy只存在内置数据类型(class object 以同一个class type object 初始化时,bitwise copy就绰绰有余了)。
memberWise copy情况下,编译器必须合成一个copy constructor以便调用member class object的copy constructor。
什么情况下,[一个类自动生成 [ 默认构造函数,拷贝构造函数 ],一个class 对于默认的copy assignment operator,不会出现bitwise copy ] ?就是这个类是一个复杂类(支持继承、多态、含有成员对象的任意情况或组合)
1.带有 [ default constructor, default copy constructor, copy assignment operator ] 的member class object / Base class。
2.带有virtual function / virtual base class的class。
带virtual function的base class object 以driver class object作为初始值,vptr指向的是base class 的virtual table。
每一个编译器对虚拟继承的支持承诺,都表示必须让“driver class object 中的 virtual base class subobject 位置”在执行期间准备妥当。
NVRO:编译器多多少少对你的程序代码做了部分转化。尤其是当一个函数以传值的方式传回一个class object ,编译器将copy constructor 的调用操作优化,以一个额外的第一参数(数值被直接存放于其中)取代NVR。
NVR:以一个额外的第一个参数copy constructor local object。
编译器会对initialization list 一一处理并可能重新排序,以反映出member 的声明次序。它会安插一些代码到constructor 体内,并置于任何explicit user code 之前。
对于初始化队列,厘清一个概念是非常重要的:(大概可以如下定义)
- 把初始化队列直接看做是对成员的定义,
- 构造函数体中进行的则是赋值操作。
关于member initialization list,我们首先需要知道需要它的时机:
1.当初始化一个reference member时;
2.当初始化一个const member时;
3.当调用一个base class的constructor,而它拥有一组参数时;
4.当调用一个member class的constructor,而它拥有一组参数时。
接下来需要注意的一点就是:list中的初始化顺序是由class中member的声明顺序决定的,而不是由initialization list中的排列顺序决定的!这是一个非常容易出错的地方。
3.Data语意学
自然多态:关于 classes 体系中base type 和derived type之间的装换,把一个derived class object指定给一个base class的指针或者引用。该操作并不需要编译器去调停编译器或修改地址。(base class 和derived class的object都从相同的地址开始)
C++的对象模型中,在一个继承而来的类的内存分布里,各个基类需要分别遵循alignment,从而导致了空间的浪费。
多重继承:需要在 &object 上偏移在base class 前面继承声明的类才能取到对应的base class 的内存。
Vptr与Vbptr
- 在多继承情况下,即使是多虚拟继承,继承而得的类只需维护一个Vbptr;
- 而多继承情况下Vptr则可能要维护多个Vptr,看其基类有几个虚函数。
- 一条继承线路只有一个Vptr,但可能有多个Vbptr,视有几次虚拟继承而定。换句话说:
- 对于继承类对象来说,不需要新合成vptr,而是使用其基类子对象的vptr。
- 而虚拟继承类对象,必须新合成一个自己的Vbptr。
其中差别在于vptr通过一个虚函数表可以确切地知道要调用的函数,而Vbptr通过虚基类表只能够知道其虚基类子对象的偏移量。这两条规则是由虚函数与虚拟继承的实现方式,以及受它们的存取方式和复制控制的要求决定的。
类中对静态成员变量取址: 对静态成员取地址,将会得到一个指向数据类型的指针,而不是一个指向class member的指针,因为静态成员并不内含与class object中.
在VC中数据成员的布局顺序为:
- vptr部分(如果基类有,则继承基类的)
- vbptr (如果需要)
- 基类成员(按声明顺序)
- 自身数据成员
- 虚基类数据成员(按声明顺序)
4.Function的语意学
多继承下的虚函数,影响到虚函数的调用的实际质上为this的调整。而this调整一般为两种:
- 调整指针指向对应的sub object,一般发生在继承类类型指针向基类类型指针赋值的情况下。
- 将指向sub object的指针调整回继承类对象的起始点,一般发生在基类指针对继承类虚函数进行调用的时候。
“指向Nonstatic Member Functions”的指针
- 取一个nonstatic data member的地址,得到的结果是该member在 class 布局中的byte位置(再加1),它是一个不完整的值,须要被绑定于某个 class object的地址上,才可以被存取.
- 取一个nonstatic member function的地址,假设该函数是nonvirtual,则得到的结果是它在内存中真正的地址.然而这个值也是不全然的,它也须要被绑定与某个 class object的地址上,才可以通过它调用该函数,全部的nonstatic member functions都须要对象的地址(以參数 this 指出).
“指向Virtual Member Functions”的指针
对一个 virtual member function取其地址,所能获得的仅仅是一个索引值。通过pmf来调用z(),会被内部转化为一个编译时期的式子,一般形式例如以下: (*ptr->vptr[(int)pmf])(ptr);
5.构造、解构、拷贝的语意学
不要在任何virtual base class中声明数据(subobject 的多重拷贝)
如果class没有定义destructor,那么只有在class自带的member object(或base class)拥有destructor的情况下,编译器才会自动合成一个destructor。
“most-derived class” 虚基类的构造由最外层类控制。
在虚拟继承情况下,copy assignment opertator会遇到一个不可避免的问题,virtual base class sub object的复制行为会发生多次,与前面说到的在虚拟继承情况下虚基类被构造多次是一个意思,不同的是在这里不能抑制非most-derived class 对virtual base class 的赋值行为。安全的做法是把虚基类的赋值放在最后,避免被覆盖。
对象析构语意学
- 只有在基类拥有析构函数,或者object member拥有析构函数的时候,编译器才为类合成析构函数,否则都被视为不需要。
- 析构的顺序正好与构造相反:
- 本身的析构函数被执行。
- 以声明的相反顺序调用member object 的析构函数,如果有的话。
- 重设vptr 指向适当的基类的虚函数表,如果有的话。
- 以声明相反的顺序调用上一层的析构函数,如果有的话。
- 如果当前类是 most-derived class,那么以构造的相反顺序调用虚基类的析构函数。
6.执行期语意学
T c = a + b 比 c = a + b有效率(一连串的 destructor 和 copy constructor 被 assigned取代)
临时性对象的被摧毁,应该是对完整表达式求值过程中的最后一个步骤。该完整表达式造成临时对象的产生。
例外:1.凡含有表达式执行结果的临时性对象,应该存留到object 的初始化操作完成为止。2.如果一个临时性对象被绑定于一个reference ,对象将残留,直到被初始化之reference 的生命结束,或直到临时对象的生命范畴结束---视哪种情况先到达而定。
7.站在对象模型的类端
1.template
具现行为:不使用不具现
错误报告:编译器只进行语法parse,不进行类型判断。
template中的名称决议方式:
1.1 scope of the template declaration :函数的调用与template参数毫无关联。
1.2 scope of the template instantiation:上反
编译器决议算法必须决定哪一个才是合适的scope, 然后再选择适当的name。
2.exception handlering
只需要处理好user code 带来的 exception。