《深度探索C对象模型》读书笔记 二.docx
- 文档编号:25623575
- 上传时间:2023-06-10
- 格式:DOCX
- 页数:11
- 大小:23.10KB
《深度探索C对象模型》读书笔记 二.docx
《《深度探索C对象模型》读书笔记 二.docx》由会员分享,可在线阅读,更多相关《《深度探索C对象模型》读书笔记 二.docx(11页珍藏版)》请在冰豆网上搜索。
《深度探索C对象模型》读书笔记二
《深度探索C对象模型》读书笔记二
2002-7-63.3DataMember的存取
1.不管什么情况,每一个staticdatamember只有一个实体,放在程序的datasegment之中,每次程序取用staticmember,不管是通过operator:
还是memberselectionoperator,都会被内部转化为对该唯一extern实体的直接参考操作。
每一个staticmember的存取以及与class的关联不会导致任何执行时间或空间上的额外负担。
如果有两个classes,每一个都声明了一个staticmemberfreeList,那么当它们都放在程序的datasegment时,就会导致名称冲突,编译器的解决方法是使用name-mangling,暗中对每一个staticdatamember编码,以获得一个独一无二的程序识别代码。
2.有多少个编译器,就有多少种name-mangling做法,任何name-mangling做法都有两个要点:
ü一种算法,推导出独一无二的名称;
ü如果编译系统或者环境工具必须和使用者交谈,那些独一无二的名称可被轻易推导回原先的名称。
3.取一个staticdatamember的地址,会得到一个指向其数据类型的常量指针,而不是指向其classmember的指针。
4.nonstaticdatamembers直接放在每一个classobject之中,除非经过显示的explicit或隐含的implicitclassobject,没有办法直接存取它们。
只要程序员在一个memberfunction中直接处理一个nonstaticdatamember,所谓implicitclassobject就会发生,其实质是编译器会为这个memberfunction增添一个constthis指针,而在函数体内通过这个this指针来存取nontaticdatamember。
5.欲对一个nonstaticdatamember进行存取操作,编译器需要把classobject的起始地址加上datamember的编译量offset,如地址&someObject.someMember等于&someobject+(&theClass:
someMember–1);指向datamember的指针,其offset值总是会被加上1,这样可以使编译系统区分出一个指向class第一个datamember的指针和一个没有指向任何datamember的指针。
6.每一个nonstaticdatamember的偏移量在编译时期即可获知,甚至如果member属于一个单一或多重继承体系中baseclasssubobject也是一样,因此其存取效率和一个Cstructmember或一个nonderivedclass的member的存取效率是一样的。
但是在虚拟继承的情况下就另当别论了:
如果该nonstaticdatamember是一个virtualbaseclass的member,并且通过指针来存取的话,在编译时期就不会得知这个member真正的offset位置,所以这个存取操作必须延迟至执行期,经由一个额外的间接导引才能够解决。
2002-7-73.4"继承"与DataMember1.在C++继承模型中,一个derivedclassobject所表现出来的东西,是其自己的members加上其baseclassesmembers的总和。
C++并未规定derivedclassmembers和baseclassesmembers的排列次序。
不过,在大部分编译器上,除virtualbaseclass外,baseclassmembers总是先出现。
2.一般而言,具体继承concreteinheritance并不会增加空间或存取时间上的额外负担。
3.把两个原本独立不相干的classes凑成一对type/subtype,并带有继承关系容易犯两个错误。
一是可能会重复设计一些相同操作的函数,一般而言,选择某些函数做成inline函数,是设计class的一个重要课题;二是把一个class分解为多层,有可能会为了表现class体系之抽象化,因为编译器的边界调整而膨胀所需空间。
其根本原因是C++保证出现在derivedclass中的baseclasssubobject有其完整原样性。
4.C++最初问世时,许多编译器把vptr放在classobject的尾端,这样可以保留baseclassCstruct的对象布局。
此后,某些编译器开始把vptr放在classobject的开始处,这样会给多重继承下通过指向classmembers之指针调用virtualfunction带来一些帮助,否则,在执行期不仅必须备妥从classobject起点处开始量起的offset,而且必须备妥classvptr之间的offset。
5.单一继承提供了一种自然多态的形态,是关于class体系中basetype和derivedtype之间的转换。
一般来说,baseclass和derivedclassobjects都是从相同的地址开始。
但若将vptr放在classobject的起始处,如果baseclass没有virtualfunction而derivedclass有,那么单一继承的自然多态就会打破。
此时,把一个derivedobject转换为其base类型就需要编译器的介入,用以调整地址。
而在既是多重继承又是虚拟继承的情况下,编译器的介入则更有必要。
6.多重继承的复杂度在于derivedclass和其上一个baseclass乃至上上一个baseclass之间的非自然关系,其主要问题发生在derivedclassobjects和其第二或后继的baseclassobjects之间的转换。
对一个多重派生对象,将其地址指定给最左端baseclass的指针,情况将和单一继承相同,而第二个或后继的baseclass的地址指定操作则需要修改地址,加上或减去(若是downcast)介于中间的baseclasssubobjects的大小。
C++并未要求多重继承时derivedclassobject中各个baseclasssubjectes的排列次序,目前各个编译器都是根据声明次序来排列它们。
7.class内如果内含一个或多个virtualbassclasssubobjects,将被分割为两部分:
一个不变局部和一个共享局部。
不变局部总是拥有固定的offset,其数据用以指定共享局部的位置,可以直接存取;而共享局部表现的就是virtualbaseclasssubobject,其位置会因为每次的派生操作而变化,只可间接存取。
各家编译器实现技术之间的差异就在于间接存取的方法不同。
8.一般而言,virtualbaseclass最有效的一种运用方式是:
一个没有任何datamember的抽象class。
2002-7-143.5对象成员的效率
如果没有把优化开关打开,就很难猜测一个程序的效率表现,因为程序代码潜在性的受到某些与编译器有关的东西的影响。
程序员如果关心效率,应该实际测试,不要光凭推论或常识判断或假设。
优化操作并不一定总是能够有效运行。
2002-7-153.6指向DataMembers的指针
指向datamembers的指针可用来详细调查classmembers的底层布局,可用来决定vptr是放在class的起始处还是尾端,还可用来决定class中accesssections的次序。
取一个nonstaticdatamember的地址,将会得到它在class的offset;而取一个staticdatamember的地址或者取一个绑定于真正classobject身上的datamember的地址,将会得到该member在内存中的真正地址。
这也正是someTypesomeClass:
*和someTye*潜在的区别。
2002-7-16Function语意学TheSemanticsofFunctionC++支持三种类型的memberfunctions:
static、nonstatic和virtual,每一种类型的调用方式都不同。
4.1Members的各种调用方式
1.C++的设计准则之一便是nonstaticmemberfunction至少必须和一般的nonmemberfunction有着相同的效率。
编译器内部会将member函数实体转换为对等的nonmember函数实体,其步骤为:
ü改写函数原型signature以安插一个额外的参数this到memberfunction中,使得classobject可以调用该函数。
其中,this是const指针,若该函数为const,则反映在this上面的结果是this指向的data也为const;
ü将每一个对nonstaticdatamember的存取操作改为经由this指针来存取;
ü将memberfunction重新写成一个外部函数,对函数名称进行mangling处理;
此后,每一个函数调用操作也都必须转换,用以提供相应的实参。
2.关于虚拟函数的内部转换步骤:
若normalize是一个virtualmemberfunction,ptr-normalize();会被内部转化为(*ptr-vptr[t])(ptr);事实上,vptr名称也会被mangled,因为可能存在有多个vptrs;t是vitrualtableslot的索引值,关联到normalize函数;第二个ptr表示this指针。
3.使用classscopeoperator明确调用一个vitualfunction,或经由一个classobject调用一个vitualfunction其决议方式会和nontaticmemberfunction一样!
故virtualfunction的一个inline函数实体可被扩展开来,因而提供极大的效率利益。
4.staticmemberfunction的主要特征是没有this指针,这导致它不能直接存取其class中的nonstaticmembers,不能被声明为const、volatile或virtual,也不需要经由classobject才能调用。
staticmemberfunction会被提出于class声明之外,并给予一个经过mangled的适当名称。
如果取一个staticmemberfunction的地址,得到的将是其在内存中的地址,其地址类型并不是一个指向classmemberfunction的指针,而是一个nonmember函数指针。
staticmemberfunction的一个意想不到的好处是可以成为一个callback函数,也可以成功地应用在thread函数身上。
2002-07-174.2VirtualMemberFunctions虚拟成员函数
1.C++中,多态polymorphism表示以一个publicbaseclass指针或reference寻址出一个derivedclassobject。
识别一个class是否支持多态,唯一适当的方法试看它是否有任何virtualfunction。
只要class拥有一个virtualfunction,它就需要一份额外的执行期型别判断信息。
2.一个class只会有一个virtualtable,其中内含对应classobject中所有的activevirtualfunctions的函数实体的地址。
这些activevirtualfunctions包括:
ü一个class定义的函数实体。
它会改写overriding一个可能存在的baseclassvirtualfunction。
ü继承自baseclass的函数实体。
此时该class不改写baseclassvirtualfunction。
ü一个pure_virtual_called()函数实体,它既可以扮演purevirtualfunction的空间保卫者,也可以当作执行期异常处理函数。
如果该函数被调用,通常的操作是结束程序。
3.每一个virtualfunction都被指派一个固定不变的索引值,该值在整个继承体系中保持与特定virtualfunction的关联。
这样就可以在编译时期设定virtualfunction的调用。
2002-7-204.多重继承下,一个上层basseclasses数目为n的derivedclass,它将内含n-1个额外的virtualtables。
其主要实体与最左端的baseclass共享,其中包含所有virtualfunctios的地址;n-1个次要实体与其它baseclasses有关,其中只包含出现在对应baseclass中virtualfunctions的地址。
5.在多重继承中支持virtualfunction,其复杂度围绕在第二个及后继baseclass上,以及执行期this指针调整上。
第二(或后继)baseclass会影响对virtualfunction支持的3种情况:
ü通过指向第二个baseclass的指针,调用derivedclassvirtualfunction;
ü通过指向derivedclass的指针,调用第二个baseclass中一个继承而来的virtualfunction;
ü允许virtualfunction函数的返回值类型有所变化,可能是basetype,也可能是publiclyderivedtype。
6.关于执行期this指针调整比较有效率的解决方法是thunk。
所谓thunk是一小端assembly码,用来以适当的offset值来调整this指针并跳到相应的virtualfunction。
thunk技术允许virtualtableslot继续内含一个简单的指针,此时多重继承将不需要任何空间上的额外负担!
slots中的地址可以直接指向virtualfunction,也可以指向一个相关的thunk。
4.3函数的效能
nonmember、staticmember和nonstaticmemberfunction在内部都会转化为完全相同的形式,三者效率相同。
2002-08-084.4指向MemberFunction的指针
对一个nonstaticmemberfunction取址,得到的是该函数在内存中的地址;而面对一个virtualfunction,得到的将是一个索引值。
这个值是不完整的,必须被绑定于一个classobject上,才能够通过它调用函数。
指向memberfunction的指针的声明语法,以及指向memberselection运算符的指针,其作用是作为this指针的空间保留者。
因此,staticmemberfunction的类型是函数指针,而不是指向memberfunction的指针。
使用一个memberfunction指针,如果并不用于virtualfunction、多重继承、virtualbaseclass等情况的话,其成本并不比使用一个nonmemberfunction指针要高。
4.5InlineFunctions
关键词inline只是一项请求。
如果在某个层次上,函数的执行成本比一般的函数调用及返回机制所带来的负荷低,那么该请求被接受,编译器就用一个表达式合理地将函数扩展开来。
真正的inline函数扩展操作是在函数调用的那一点上。
在inline扩展期间,每一个形式参数会被对应的实际参数所取代,inline函数中的每一个局部变量都必须被放在函数调用的一个封闭区段中,并拥有一个独一无二的名称。
这会带来参数的求值操作以及临时性对象的管理。
2002-08-11
构造、解构、拷贝语意学SemanticsofConstruction,Destruction,andCopy1.一般而言,class的datamember应该被初始化,而且只在constructor中或其它memberfunctions中初始化,其它任何操作都将破坏其封装性质,使其维护和修改更加困难。
2.可以定义并调用invoke一个purevirtualfunction,但它只能被静态调用,不能经由虚拟机制调用。
每一个derivedclassdestructor会被编译器加以扩展,静态调用每一个virtualbaseclass以及上一层baseclass的destructor。
因此,不管baseclass的virtualdestructor是否声明为pure,它必须被定义。
5.1无继承情况下的对象构造
C++Standard要求编译器尽量延迟nontrivialmembers的实际合成操作,直到真正遇到其使用场所为止。
5.2继承体系下的对象构造
一般而言,继承体系下编译器对constructor所作的扩充操作以及次序大约如下:
ü所有virtualbaseclassconstructors必须从左到右、从深到浅被调用:
如果class被列于memberinitializationlist中,那么任何明确指定的参数都必须传递过去,否则如果class有一个defaultconstructor,也应该调用它;class中的每一个virtualbaseclasssubobject的偏移量offset必须在执行期可被存取;如果classobject是最底层most-derived的class,其constructors可能被调用,某些用以支持这个行为的机制必须被方进来。
ü以baseclass的声明次序调用上一层baseclassconstructors:
如果baseclass被列于memberinitializationlist中,那么任何明确指定的参数都必须传递过去,否则若它有defaultconstructor或defaultmemberwisecopyconstructor,那么就调用它;如果baseclass是多重继承下的第二或后继的baseclass,那么this指针必须有所调整。
ü如果classobject有virtualtablepointer(s),它(们)必须被设定初值,指向适当的virtualtable(s)。
ü如果有一个member没有出现在memberinitializationlist中,但它有defaultconstructor,调用之。
ü将memberinitializationlist中的datamembers的初始化操作以members的声明次序放进constructor的函数本身。
2002-8-185.3对象复制语意学ObjectCopySemantics1.只有在默认行为所导致的语意不安全或者不正确以致发生别名化aliasing或者内存泄漏memoryleak时,才需要设计一个copyassignmentoperator。
否则,程序反倒会执行得较慢。
2.如果仅仅是为了把NRV优化开关打开而提供一个copyconstructor,那么就没有必要一定要提供一个copyassignmentoperator。
3.copyassignmentoperator有一个非正交情况,那就是它缺乏一个平行于memberinitializationlist的memberassignmentlist。
调用baseclass的copyassignmentoperator示例:
Point:
operator=(p3d);或(*(Point*)this)=p3d;或(Point&)(*this)=p3d;
4.事实上,copyassignmentoperator在虚拟继承情况下行为不佳,需要小心设计和说明。
许多编译器甚至并不尝试取得正确的语意,它们在每一个中间的copyassignmentoperator中调用每一个baseclassinstance,于是造成virtualbasecopyassignmentoperator的多个实体被调用。
建议尽可能不要允许一个virtualbaseclass的拷贝操作,并不要在任何virtualbaseclass中声明datamember。
5.5解构语意学SemanticsofDestruction
如果class没有定义destructor,那么只有在其内带的memberobject或baseclass拥有destructor时,编译器才会自动合成出一个destructor。
一个由程序员定义的destructor被扩展的方式类似constructors被扩展的方式,只是顺序相反:
üdestructor的函数本体首先被执行;
ü如果class拥有memberclassobjects,而后者拥有destructors,那么它们将以声明的相反顺序而调用;
ü如果object内带一个vptr,则现在被重新设定以指向适当baseclass之virtualtable;
ü如果有任何直接的nonvirtualbaseclasses拥有destructor,它们将以声明的相反顺序而调用;
ü如果有任何virtualbaseclasses拥有destructor,而前面讨论的这个class是most-derivedclass,那么它们会以原先构造顺序的相反顺序被调用。
2002-8-19
执行期语意学RuntimeSemantics6.1对象的构造和解构
1.一般而言,constructor和destructor的安插都如你所预期。
但如果一个区段或函数中有一个以上的离开点,情况就会复杂一些,destructor会放在每一个离开点之前。
通常,我们要求将object尽可能放在使用它的那个程序区附近,这样做可以节省不必要的对象产生和销毁操作。
2.C++程序中所有的globalobjects都被放置在程序的datasegment中,如果不明确指定初值,object所配置的内存内容将为0(C并不自动设定初值)。
如果globalobject有constructor和destructor的话,我们说它需要静态的初始化和内存释放操作。
2002-8-203.virtualbaseclass的subobject在每个derivedclass中的位置可能会变动,不能在编译时期确定。
以一个derivedclass的pointer或reference来存取virtualbaseclasssubobject,是一种nonconstantexpression,必须在执行期方可评估求值。
4.使用静态初始化的object有一些缺点。
其一,无法放入try区段,任何throw操作必将触发exceptionhandlinglibrary的默认函数terminate();其二,程序员必须为控制"需要跨越模块做静态初始化"objects的依赖顺序而产生的复杂度付出代价。
建议根本就不要使用那些需要静态初始化的globalobjects。
5.新的C++标准要求编译单位中的staticlocalclassobjects必须在相应函数第一次被调用时才被构造,而且必须以相反的次序销毁。
由于这些objects是在需要时才被
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 深度探索C 对象模型 深度探索C 对象模型读书笔记 深度 探索 对象 模型 读书笔记