深度探索C++对象模型第一章关于对象C++对象模型语言中直接支持面向对象程序设计的部分。
对于各种支持的底层实现机制。
第I个概念是一种不变量。
例如,c++class的完整virtual function在编译时期就固定下来了,程序员没有办法在执行期动态增加或取代其中的某一个。
对象模型的底层实现机制,在语言层面上是看不出来的—虽然对象模型的语义本身可以使得某些实现品(编译器)比其他实现品更接近自然。
C++在布局以及存取时间上主要的额外负担是由virtual引起的Virtual function机制用以支持一个由效率的“执行期绑定”Virtual bas class 用以实现“多次出现在继承体系中的base class”有一个单一而被共享的实例。
简单对象模型:一个object是一系列的slots,每一个slot指向一个members。
表格驱动对象模型:C++对象模型:Nonstatic data members被配置于每一个class object之内,static data members则被存放在个别的class object之外。
Static和nonstatic function members被放在object之外。
对象的差异C++程序设计模型直接支持三种programming paradigs:程序模型,抽象数据了类型模型,面向对象模型。
C++以下支持多态:经由一组隐式的转化操作。
经由virtual function机制。
经由dynamic_cast和typeid运算符。
一个class object的内存:其nonstatic data members的总和大小。
加上任何由于alignment的需求而填补上去的空间加上为了支持virtual而由内部产生的任何额外负担。
指针的类型指向不同类型之各指针间的差异,既不在其指针表示法不同,也不在其内容(代表一个地址)不同,而是在其所寻址出来的object类型不同。
指针类型会教导编译器如何解释某个特定地址的内存内容及其大小:一个指向地址1000的整数指针,在32为机器上,将涵盖地址空间1000~1003。
一个指向地址1000而类型为void*的指针,涵盖的地址空间我们无法得知。
这就是为什么一个类型为void*的指针只能够持有一个地址,而不能够通过它操作所指之object的缘故。
转换其实是一种编译器指令。
大部分情况下它并不改变一个指针所含的真正的地址,它只影响被指出之内存的大小和其内容的解释方式。
多态所造成的一个以上的类型的潜在力量,并不能够实际发挥在“直接存取object”这件事情上。
一个pointer或一个refrence之所以支持多态,是因为它们并不引发内存中任何与类型有关的内存委托操作;会受到改变的,只有它们所指向的内存的“大小和内容解释方式”。
任何企图改变object的大小,会违反其定义中受到契约保护的“资源需求量”当基类直接初始化给派生类对象时,派生类对象会发生切割。
反之会发生溢出。
第二章构造函数语义学当编译器需要default constructor时会合成它。
对于一个类,如果没有任何user-declared constructor,那么会有一个default construct被隐式声明出来下面四种情况下implicit default constructor 会被视为nontrivial带有default construct的member class object如果一个class没有任何constructor,但它内含一个member object,而后者有default constructor,那么这个class的implicit default constructor就是nontrivial,编译器需要为该class 合成出一个default constructor。
不过这个合成操作只有在constructor真正需要被调用时才会发生。
如果一个类内含一个或一个以上的member class objects,那么类的每一个constructor必须调用每一个member class的default construct。
编译器会扩张已存在的constructors,在其中安插一些代码,使得user code被执行之前,先调用default constructors。
如果class member objects都要求constructor初始化操作,c++要求以member object在class 中的声明顺序来调用各个constructors。
带有default constructor的base class带有一个virtual function的class一个virtual function table会被编译器产生出来,内放class的virtual function地址。
在每一个classobject中,一个额外pointer member会被编译器合成出来内含相关之class vtbl 的地址。
带有一个virtual base class的class在派生类对象的每一个virtual base classes中安插一个指针完成。
所有经由reference或pointer来存取一个virtual base class的操作都可以通过相关指针完成。
Pa->_vbcx->i其中_vbcx表示编译器所产生的指针,指向virtual base class。
Implicit trivial default constructor实际上并不会被合成出来。
拷贝构造函数如果class没有提供一个explicit copy construct,当class object以相同class的另一个object 作为初值,其内部是以所谓的default memberwise initialization手法完成的,也就是把每一个内建的或派生的data member的值从某个object拷贝一份到另一个object身上。
不过它并不会拷贝其中的member class object,而是以递归的方式施行memberwise initialization。
Default constructors和copy constructors在必要的时候才由编译器产生出来。
就是指当class 不展现bitwise copy semantic。
位逐次拷贝:进行简单的复制。
一下四种情况不展现出位逐次拷贝:当class内含一个member object而后者的class声明一个copy constructor时。
当class继承自一个base class而后者存在一个copy constructor时(无论是显示还是隐式)当class声明一个或多个virtual function时。
当class派生自一个继承串链,其中有一个多个virtual base classes。
合成出来的父类拷贝构造函数会显示设定class object的vptr指向父类的virtual table,而不是直接从右手边的class object中其vptr现值拷贝过来。
一个virtual base class的存在会使bitwise copy semantic无效。
问题并不发生于一个class object以另一个同类的object作为初值之时,而是发生于一个class object以其derived classes 的某个object作为初值时。
程序转化语义学参数初始化:void foo(X x0);X xx;foo(xx);转换代码如下:X _temp0;_temp0.X::X(xx); //编译器对copy constructor的调用foo(_temp0); //重新改写函数调用操作,以便使用上述的临时对象;foo()的声明因而也必须被转化,形式参数必须从原先的一个class X object改变为一个class X reference,void foo(X &x0);另一种实现方法是以“拷贝构建”的方式把实际参数直接构建在其应该的位置上,此位置视函数活动范围的不同,记录于程序堆栈中,在函数返回之前,局部对象的destructor会被执行。
返回值初始化:X bar(){X xx;return xx;}首先加上一个额外参数,类型是class object的一个reference。
这个参数将用来放置被“拷贝建构”而得的返回值。
在return指令之前安插一个copy constructor调用操作,以便将欲传回之object的内容当作上述新增参数的初值。
转换代码如下:void bar(X&_result){X xx;xx.X::X();_result.X::XX(xx);return;}X xx=bar();转换为:X xx; bar(xx); 不必施行default constructor而bar.memfun();可转化为:X _temp0;(bar(_temp0),_temp0).memfun();当class需要大量的memberwise初始化操作,那么提供一个copy constructor的explicit inline 函数实例就非常合理copy constructor的应用,迫使编译器多多少少对你的程序代码做部分优化。
尤其当一个函数以传值的方式传回一个class object,而该class有一个copy constructor时(不论是显示定义出来还是合成的),这将导致深奥的程序调用操作优化,以一个额外的第一参数(数值直接存在其中)取代NRV。
程序员如果了解那些转换,以及copy constructor优化后的可能状态,就比较能够控制其程序的执行效率。
成员函数的初始化队伍当有一下四种情况时,可以考虑使用member initialization list当初始化一个reference member时;当初始化一个const member时;当调用一个base class的construct,而它拥有一组参数时;当调用一个member class的constructor,而它拥有一组参数时;在这四种情况下程序可以正确的被编译并执行,但是执行效率不高。
class Word{String s;int I;public:Word(){s=0;i=0;}};伪代码:Word::Word(){s.String::String();String _temp=String(0);s.String::oprator=(_temp);_temp.String::~String();i=0;}而如果用初始化列表的话伪代码如下:Word::Word(){s.String::String(0);i=0;}initialization list的项目被放在explicit user code之前;第三章Data语意学class X{};它有一个隐藏的1byte大小,那是被编译器安插进去的一个char。