欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

关于C++对象模型的深入理解

程序员文章站 2023-02-08 09:07:16
1.任何对象模型都需要的三种转换风味: ü与编译器息息相关的转换 ü语言语义转换 ü程序代码和对象模型的转换 2.c++对象模型的两种解释 ü语言中直接支持面...

1.任何对象模型都需要的三种转换风味:

ü与编译器息息相关的转换

ü语言语义转换

ü程序代码和对象模型的转换

2.c++对象模型的两种解释

ü语言中直接支持面向对象程序设计的部分

ü对于各种支持的底层实现机制

3.c++ class的完整virtual functions在编译时期就固定下来了,程序员没有办法在执行期动态增加或取代其中某一个。这使得虚拟函数调用操作得以有快速的派送结果,付出的却是执行期的弹性。

4.目前所有编译器对于virtual function的实现都是使用各个class专属的virtual table,大小固定,并且在程序执行前就构造好了。

5.c++对象模型的底层机制并未标准化,它会因实现品(编译器)和时间的变动而不同。

2002-6-23

关于对象object lessons

1.1 c++对象模式

1.c++在布局以及存取时间上主要的额外负担是由virtual引起的,包括virtual function机制和virtual base class机制,还有一些发生在“一个derived class和其第二或后继之base class的转换”上的多重继承。

2.在c++对象模型中,nonstatic data members被配置于每一个class object之内,static data members则被存放在所有的class object之外,static和nonstatic function members也被放在所有的class object之外,virtual functions则以两个步骤支持:每个class产生一堆指向virtual functions的指针,放在virtual table (vtbl)中;每个class object被添加一个指针vptr,指向相关的virtual table。每个class所关联的type_info object也经由vtbl指出,通常是放在vtbl的第一个slot处。vptr由每一个class的construtor、destructor以及copy assignment operator自动完成。以上模型的主要优点在于空间和存取时间的效率,主要缺点是,只要应用程序所用到的class object的nonstatic data members有所修改,那么应用程序代码就必须重新编译。

3.c++最初所采用的继承模型并不运用任何间接性,base class subobject的data members直接放置于derived class object中。优点是提供对base class members紧凑且高效的存取,缺点是base class members的任何改变,都将导致使用其derived class的object的应用程序代码必须重新编译。

4.virtual base class的原始模型是在class object中为每一个有关联的virtual base class加上一个指针,其他演化出来的模型不是导入一个virtual base class table,就是扩充原已存在的vtbl,用以维护每一个virtual base class的位置。

1.2关键词所带来的差异

1.可以说关键词struct的使用伴随着一个public接口的声明,也可以说它的用途只是为了方便c程序员迁徙至c++部落。

2.c++中凡处于同一个access section的数据,必定保证以声明次序出现在内存布局中,然而被放在多个access sections中的各笔数据排列次序就不一定了。同样,base classes和derived classes的data members的布局也没有谁先谁后的强制规定。

3.组合composition而非继承才是把c和c++结合在一起的唯一可行方法。

1.3对象的差异

1.c++程序设计模型支持三种程序设计典范programming paradigms:

ü程序模型procedural model

ü抽象数据类型模型abstract data type model,adt

ü面向对象数据模型object-oriented model,oo

2.虽然可以直接或间接处理继承体系中的一个base class object,但只有通过pointer或reference的间接处理,才能支持oo程序设计所需的多态性质。

3.c++中,多态只存在于public class体系中,nonpublic的派生行为以及类型为void*的指针可以说是多态,但它们没有被语言明白地支持,必须由程序员通过显示的转型操作来管理。

4.c++以下列方法支持多态:

ü经由一组隐含的转化操作,如把一个derived class指针转化为一个指向其public base type的指针;

ü经由制;

ü经由dynamic_cast和typeid运算符。

5.多态的主要用途是经由一个共同的接口来影响类型的封装,这个接口通常被定义在一个抽象的base class中。这个接口是以virtual function机制引发的,它可以在执行期根据object的真正类型解析出到底是哪一个函数实体被调用。

6.一个class object所需的内存,一般而言由以下部分组成:

ünonstatic data members的总和大小;

ü任何由于alignment需求而填补上去的空间;

ü为支持virtual而由内部产生的任何额外负担。

7.一个pointer或reference,不管它指向哪一种数据类型,指针本身所需的内存大小是固定的。本质上,一个reference通常是以一个指针来实现,而object语法如果转换为间接手法,就需要一个指针。

8.指向不同类型之指针的差异,既不在其指针表示法不同,也不在其内容不同,而是在其所寻址出来的object类型不同,亦即指针类型会教导编译器如何解释某个特定地址中的内存内容及大小。它们之所以支持多态,是因为它们并不引发内存中任何与类型有关的内存委托操作,会受到改变的只是它们所指向的内存的大小和内容的解释方式。

9.转型cast操作其实是一种编译指令,大部分情况下它并不改变一个指针所含的真正地址,它只是影响被指向之内存的大小和内容的解释方式。

10.一个base class object被直接初始化或指定为一个derived object时,derived object就会被切割sliced,以塞入较小的base type内存中,多态于是不再呈现。一个严格的编译器可以在编译时期解析一个通过该object而触发的virtual function调用操作,从而回避virtual机制。这时,如果virtual function被定义为inline,则会有效率上的收获。

11.c++通过class的pointer和reference来支持多态,这种程序设计风格就是所谓的oo;c++也支持具体的adt程序风格,如今被称为object-based ob,不支持多态,不支持类型的扩充。

2002-6-25

构造函数语意学the semantics of constructors

1.jerry schwarz,iostream函数库建构师,曾为了让cin能够求得一个真假值,于是他为它定义了一个conversion运算符operator int()。但在语句cin << intval中,其行为出乎意料:程序原本要的是cout而不是cin!但是编译器却找到一个正确的诠释:将cin转型为整型,现在left shift operator <<就可以工作了!这就是所谓的“schwarz error”。jerry最后以operator void *()取代operator int()。

2.引入关键词explicit的目的,就是为了提供程序员一种方法,使他们能够制止单一参数的constructor被当作一个conversion运算符。其引入是明智的,但其测试应该是残酷的!

2.1 default constructor的建构操作

1.global objects的内存保证会在程序激活的时候被清为0。local objects配置于程序的堆栈中,heap objects配置于*空间中,都不一定会被清为0,它们的内容将是内存上次被使用后的遗迹。

2.在各个不同的版本模块中,编译器避免合成出多个default constructor的方法:把合成的default constructor、copy constructor、assignment copy operator都以inline方式完成。一个inline函数有静态链接,不会被档案以外者看到。如果函数过于复杂,不适合做成inline,就会合成一个explicit non-inline static实体。

3.以下四种情况,编译器必须为未声明constructor的classes合成一个implicit nontrivial default constructor:带有default constructor的member class object,带有default constructor的base class,带有virtual function,带有virtual base class。其它各种情况且没有声明任何constructor的classes,它们拥有的是implicit trival default constructors,它们实际上并不会被合成出来。

4.编译器合成implicit nontrivial default constructor,不过是暗地里作了一些重要的事情以保证程序正确合理地运行。如果程序员提供了多个constructors,但其中都没有default constructor,编译器同样会在这些constructors中插入一些相同功能的代码,这些代码都将被安插在explicit user code之前。

2002-6-26

2.2 copy constructor的建构操作

1.有三种情况,会以一个class的内容作为另一个class object的初值:

ü对一个object作明确的初始化操作,如:someclass obt = obtb;

ü一个object被当作参数交给某个函数时,如:foo(obt);

ü当函数返回一个class object时。

若class设计者明确定义了一个copy constructor,大部分情况下,该constructor会被调用。这可能导致一个暂时性的class object的产生或程序代码的蜕变,或者两者皆有。

2.如果class没有提供一个explicit copy constructor,当class object以相同class的另一个object作为初值时,其内部是以所谓的default memberwise initialization手法完成的,即把每一个内建的或派生的data member的值,从一个object拷贝到另一个object。不过,它并不会拷贝其中的member class object,而是以递归的方式施行memberwise initialization。

3.一个class object可以从两种方式复制得到:初始化和指定,从概念上而言,这两个操作分别是以copy constructor和copy assignment operator完成的。

4.如果class没有声明一个copy constructor,就会有隐含的声明implicitly declared或隐含的定义implicitly defined出现。c++把copy constructor分为trivial和nontrivial两种。只有nontrivial的实体才会被合成出来。决定一个copy constructor是否为trivial的标准在于class是否展现出所谓的“bitwise copy semantics”。

5.以下四种情况,一个class不展现bitwise copy semantics:

üclass内含一个member object而后者的class声明有或被编译器合成有一个copy constructor时;

üclass继承自一个base class而后者存在或被编译器合成有一个copy constructor时;

ü当class声明了一个或多个virtual functions时;

ü当class派生自一个继承串链,其中有一个或多个virtual base classes时。

前两种情况中,编译器必须将member或base class的copy constructors调用操作安插到被合成的copy constructor中。

6.一旦一个class object中必须引入vptr,编译器就必须为它的vptr正确地设置好初值。此时,该class就不再展现bitwise semantics。

7.当一个base class object以其derived class object内容作初始化操作时,其vptr复制操作必须保证安全。

8.每一个编译器对于虚拟继承的承诺,都表示必须让derived class object中的virtual base class subobject的位置在执行期准备妥当。维护位置的完整性是编译器的责任。

2002-6-27

2.3程序转化语意学

1.每一个明确的初始化操作都会有两个必要的程序转化阶段:先重写每一个定义,剥除其中的初始化操作,然后安插class的copy constructor调用操作。

2.把一个class object当作参数传给一个函数或是作为一个函数的返回值,相当于以下形式的初始化操作:

x xx = arg;其中xx代表形式参数或返回值,而arg代表真正的参数值。

3.函数定义如下:x bar(){x xx; return xx;},bar()的返回值通过一个双阶转化从局部对象xx中拷贝出来:

ü首先为bar添加一个额外参数,类型是class object的一个reference,这个参数用来放置被拷贝构建而得的返回值。

ü然后在return指令之前安插一个copy constructor调用操作,以便将欲传回之object的内容当作上述新增参数的初值,同时重写函数使它不返回任何值。

4.named return value(nrv)优化如今被视为是标准c++编译器的一个义不容辞的优化操作,它的特点是直接操作新添加的额外参数。注意只有copy constructor的出现才会激活c++编译器的nrv优化!nrv优化虽然极大地改善了效率,但还是饱受批评:一是优化由编译器默默完成,而是否完成以及其完成程度完全透明;二是一旦函数变得比较复杂,优化就变得较难施行;三是优化由可能使程序产生错误——有时并不是对称地调用constructor和destructor,而是copy constructor未被调用!

5.在编译器提供nrv优化的前提下,如果可以预见class需要大量的memberwise初始化操作,比如以by value的方式传回objects,那么提供一个explicit inline copy constructor的函数实体就非常合理。此种情况下,没有必要同时提供explicit assignment operator定义。

6.copy constructor的应用迫使编译器多多少少对程序代码作部分优化,尤其是当一个函数以by value的方式传回一个class object,而该class有一个copy constructor(或定义或合成)时,无论在函数的定义还是在使用上将导致深奥的程序转化。此外,编译器将实施nrv优化。

7.注意正确使用memset()和memcpy(),它们都只有在classes不含任何由编译器产生的内部members如vptr时才能有效运行!

2002-6-30

2.4成员初始化列表

1.当写下一个constructor时,就有机会设定class members的初值。不是经由member initialization list,就是在constructor函数本身之内。

2.下列情况,为了让程序能被顺利编译,必须使用member initialization list:

ü初始化一个reference member时;

ü初始化一个const member时;

ü调用一个base class的constructor,而它拥有一组参数时;

ü调用一个member class的constructor,而它拥有一组参数时。

3.编译器会对initialization list一一处理并可能重新排序,以反映出members的声明次序,它会安插一些代码到constructor内,并置于任何explicit user code之前。

4.一个忠告:请使用“存在于constructor体内的一个member”,而不是“存在于member initialization list中的一个member”,来为另一个member设定初值。

2002-7-1

data语意学the semantics of data

讨论如下继承体系:

class x{};

class y : public virtual x{};

class z : public virtual x{};

class a: public y, public z{};

1.一个empty class如class x{},它有一个隐晦的1 byte,那是被编译器安插进去的一个char,使得这个class的两个objects得以在内存中配置独一无二的地址。

2.y和z的大小受到三个因素的影响:

ü语言本身所造成的额外负担overhead。语言支持virtual base classes时导致的额外负担反映在某种形式的指针身上,它要么指向virtual base class subobject,要么指向一个存放virtual base class subobject地址或者其偏移量offset的表格。

ü编译器对于特殊情况所提供的优化处理。virtual base class x 1 byte大小的subobject也出现在class y和z身上。传统上它被放在derived class的固定部分的尾端。某些编译器对empty virtual base提供特殊处理,将它视为derived class object最开头的一部分,它不用会任何的额外空间,也就是前面提到的1 byte。

üalignment的限制。alignment就是将数值调整到某数的整数倍,在32位计算机上,通常该数为4 bytes(32位),以使bus的运输量达到最高效率。

3.一个virtual base class subobject只会在derived class中存在一份实体,不管它在class继承体系中出现了多少次,class a的大小由下列几点决定:

ü被大家共享的唯一一个class x实体,大小为1 byte;

übase y、z的大小减去因virual base class而配置的大小;

üclass a自己的大小;

üclass a的alignment数量。

4.c++ standard并不强制规定base class subobjects、不同存取级别的data members的排列次序这种琐碎细节,它也不规定virtual function以及virtual base classes的实现细节。

5.c++对象模型尽量以空间优化和存取速度优化来表现nonstatic data members,并且保持和c语言struct数据配置的兼容性。它把数据直接存放在每一个class object中,对于继承而来的nonstatic data members,不管是virtual或nonvirtual base class也是如此。至于static data members则被放置在程序的一个global data segment中,不会影响个别class object的大小。static data member永远只存在一份实体,但是一个template class的static data member的行为稍有不同。

3.1 data member的绑定

inline member function躯体内的data member绑定操作,会在整个class声明完成后才发生,而argument list中的名称还是会在它们第一次遭遇时被适当地决议resolved完成。基于这种状况,请始终把nested type声明放在class的起始处。

2002-7-2

3.2 data member的布局

1.每一个private、protected、public区段就是一个access section。c++ standard要求,在同一个access section中,members的排列只需满足“较晚出现的members在class object中有较高的地址”这一条件即可。也就是说各个members并不一定的连续排列,alignment可能需要的bytes以及编译器可能合成供内部使用的data members都可能介于被声明的members之间。

2.c++ standard也允许编译器将多个access sections之中的data members*排列,不必在乎它们出现在class声明中的次序。当前各家编译器都是把一个以上的access sections连锁在一起,依照声明的次序成为一个连续区块。access sections的多寡不会导致额外负担。

3.vptr传统上会被放在所有明确声明的members的最后,不过如今也有一些编译器把vptr放在class object的最前端。

4.一个用来判断哪一个section先出现的template function:

template

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”;

}

(我在vc++ 6.0下测试该函数,编译尚未通过。这个,应该怪罪编译器)

2002-7-6

3.3 data member的存取

1.不管什么情况,每一个static data member只有一个实体,放在程序的data segment之中,每次程序取用static member,不管是通过operator::还是member selection operator,都会被内部转化为对该唯一extern实体的直接参考操作。每一个static member的存取以及与class的关联不会导致任何执行时间或空间上的额外负担。如果有两个classes,每一个都声明了一个static member freelist,那么当它们都放在程序的data segment时,就会导致名称冲突,编译器的解决方法是使用name-mangling,暗中对每一个static data member编码,以获得一个独一无二的程序识别代码。

2.有多少个编译器,就有多少种name-mangling做法,任何name-mangling做法都有两个要点:

ü一种算法,推导出独一无二的名称;

ü如果编译或者环境工具必须和使用者交谈,那些独一无二的名称可被轻易推导回原先的名称。

3.取一个static data member的地址,会得到一个指向其数据类型的常量指针,而不是指向其class member的指针。

4.nonstatic data members直接放在每一个class object之中,除非经过显示的explicit或隐含的implicit class object,没有办法直接存取它们。只要程序员在一个member function中直接处理一个nonstatic data member,所谓implicit class object就会发生,其实质是编译器会为这个member function增添一个const this指针,而在函数体内通过这个this指针来存取nontatic data member。

5.欲对一个nonstatic data member进行存取操作,编译器需要把class object的起始地址加上data member的编译量offset,如地址&someobject.somemember等于&someobject + (&theclass::somemember – 1);指向data member的指针,其offset值总是会被加上1,这样可以使编译系统区分出一个指向class第一个data member的指针和一个没有指向任何data member的指针。

6.每一个nonstatic data member的偏移量在编译时期即可获知,甚至如果member属于一个单一或多重继承体系中base class subobject也是一样,因此其存取效率和一个c struct member或一个nonderived class的member的存取效率是一样的。但是在虚拟继承的情况下就另当别论了:如果该nonstatic data member是一个virtual base class的member,并且通过指针来存取的话,在编译时期就不会得知这个member真正的offset位置,所以这个存取操作必须延迟至执行期,经由一个额外的间接导引才能够解决。

2002-7-7

3.4“继承”与data member

1.在c++继承模型中,一个derived class object所表现出来的东西,是其自己的members加上其base classes members的总和。c++并未规定derived class members和base classes members的排列次序。不过,在大部分编译器上,除virtual base class外,base class members总是先出现。

2.一般而言,具体继承concrete inheritance并不会增加空间或存取时间上的额外负担。

3.把两个原本独立不相干的classes凑成一对type/subtype,并带有继承关系容易犯两个错误。一是可能会重复设计一些相同操作的函数,一般而言,选择某些函数做成inline函数,是设计class的一个重要课题;二是把一个class分解为多层,有可能会为了表现class体系之抽象化,因为编译器的边界调整而膨胀所需空间。其根本原因是c++保证出现在derived class中的base class subobject有其完整原样性。

4.c++最初问世时,许多编译器把vptr放在class object的尾端,这样可以保留base class c struct的对象布局。此后,某些编译器开始把vptr放在class object的开始处,这样会给多重继承下通过指向class members之指针调用virtual function带来一些帮助,否则,在执行期不仅必须备妥从class object起点处开始量起的offset,而且必须备妥class vptr之间的offset。

5.单一继承提供了一种自然多态的形态,是关于class体系中base type和derived type之间的转换。一般来说,base class和derived class objects都是从相同的地址开始。但若将vptr放在class object的起始处,如果base class没有virtual function而derived class有,那么单一继承的自然多态就会打破。此时,把一个derived object转换为其base类型就需要编译器的介入,用以调整地址。而在既是多重继承又是虚拟继承的情况下,编译器的介入则更有必要。

6.多重继承的复杂度在于derived class和其上一个base class乃至上上一个base class之间的非自然关系,其主要问题发生在derived class objects和其第二或后继的base class objects之间的转换。对一个多重派生对象,将其地址指定给最左端base class的指针,情况将和单一继承相同,而第二个或后继的base class的地址指定操作则需要修改地址,加上或减去(若是downcast)介于中间的base class subobjects的大小。c++并未要求多重继承时derived class object中各个base class subjectes的排列次序,目前各个编译器都是根据声明次序来排列它们。

7.class内如果内含一个或多个virtual bass class subobjects,将被分割为两部分:一个不变局部和一个共享局部。不变局部总是拥有固定的offset,其数据用以指定共享局部的位置,可以直接存取;而共享局部表现的就是virtual base class subobject,其位置会因为每次的派生操作而变化,只可间接存取。各家编译器实现技术之间的差异就在于间接存取的方法不同。

8.一般而言,virtual base class最有效的一种运用方式是:一个没有任何data member的抽象class。

2002-7-14

3.5对象成员的效率

如果没有把优化开关打开,就很难猜测一个程序的效率表现,因为程序代码潜在性的受到某些与编译器有关的东西的影响。程序员如果关心效率,应该实际测试,不要光凭推论或常识判断或假设。优化操作并不一定总是能够有效运行。

2002-7-15

3.6指向data members的指针

指向data members的指针可用来详细调查class members的底层布局,可用来决定vptr是放在class的起始处还是尾端,还可用来决定class中access sections的次序。

取一个nonstatic data member的地址,将会得到它在class的offset;而取一个static data member的地址或者取一个绑定于真正class object身上的data member的地址,将会得到该member在内存中的真正地址。这也正是sometype someclass::*和sometye *潜在的区别。

2002-7-16

function语意学the semantics of function

c++支持三种类型的member functions:static、nonstatic和virtual,每一种类型的调用方式都不同。

4.1 members的各种调用方式

1.c++的设计准则之一便是nonstatic member function至少必须和一般的nonmember function有着相同的效率。编译器内部会将member函数实体转换为对等的nonmember函数实体,其步骤为:

ü改写函数原型signature以安插一个额外的参数this到member function中,使得class object可以调用该函数。其中,this是const指针,若该函数为const,则反映在this上面的结果是this指向的data也为const;

ü将每一个对nonstatic data member的存取操作改为经由this指针来存取;

ü将member function重新写成一个外部函数,对函数名称进行mangling处理;

此后,每一个函数调用操作也都必须转换,用以提供相应的实参。

2.关于虚拟函数的内部转换步骤:若normalize是一个virtual member function,ptr->normalize();会被内部转化为(*ptr->vptr[t])(ptr);事实上,vptr名称也会被mangled,因为可能存在有多个vptrs;t是vitrual table slot的索引值,关联到normalize函数;第二个ptr表示this指针。

3.使用class scope operator明确调用一个vitual function,或经由一个class object调用一个vitual function其决议方式会和nontatic member function一样!故virtual function的一个inline函数实体可被扩展开来,因而提供极大的效率利益。

4.static member function的主要特征是没有this指针,这导致它不能直接存取其class中的nonstatic members,不能被声明为const、volatile或virtual,也不需要经由class object才能调用。static member function会被提出于class声明之外,并给予一个经过mangled的适当名称。如果取一个static member function的地址,得到的将是其在内存中的地址,其地址类型并不是一个指向class member function的指针,而是一个nonmember函数指针。static member function的一个意想不到的好处是可以成为一个callback函数,也可以成功地应用在thread函数身上。

2002-07-17

4.2 virtual member functions虚拟成员函数

1.c++中,多态polymorphism表示以一个public base class指针或reference寻址出一个derived class object。识别一个class是否支持多态,唯一适当的方法试看它是否有任何virtual function。只要class拥有一个virtual function,它就需要一份额外的执行期型别判断信息。

2.一个class只会有一个virtual table,其中内含对应class object中所有的active virtual functions的函数实体的地址。这些active virtual functions包括:

ü一个class定义的函数实体。它会改写overriding一个可能存在的base class virtual function。

ü继承自base class的函数实体。此时该class不改写base class virtual function。

ü一个pure_virtual_called()函数实体,它既可以扮演pure virtual function的空间保卫者,也可以当作执行期异常处理函数。如果该函数被调用,通常的操作是结束程序。

3.每一个virtual function都被指派一个固定不变的索引值,该值在整个继承体系中保持与特定virtual function的关联。这样就可以在编译时期设定virtual function的调用。

2002-7-20

4.多重继承下,一个上层basse classes数目为n的derived class,它将内含n-1个额外的virtual tables。其主要实体与最左端的base class共享,其中包含所有virtual functios的地址;n-1个次要实体与其它base classes有关,其中只包含出现在对应base class中virtual functions的地址。

5.在多重继承中支持virtual function,其复杂度围绕在第二个及后继base class上,以及执行期this指针调整上。第二(或后继)base class会影响对virtual function支持的3种情况:

ü通过指向第二个base class的指针,调用derived class virtual function;

ü通过指向derived class的指针,调用第二个base class中一个继承而来的virtual function;

ü允许virtual function函数的返回值类型有所变化,可能是base type,也可能是publicly derived type。

6.关于执行期this指针调整比较有效率的解决方法是thunk。所谓thunk是一小端assembly码,用来以适当的offset值来调整this指针并跳到相应的virtual function。thunk技术允许virtual table slot继续内含一个简单的指针,此时多重继承将不需要任何空间上的额外负担!slots中的地址可以直接指向virtual function,也可以指向一个相关的thunk。

4.3函数的效能

nonmember、static member和nonstatic member function在内部都会转化为完全相同的形式,三者效率相同。

2002-08-08

4.4指向member function的指针

对一个nonstatic member function取址,得到的是该函数在内存中的地址;而面对一个virtual function,得到的将是一个索引值。这个值是不完整的,必须被绑定于一个class object上,才能够通过它调用函数。指向member function的指针的声明语法,以及指向member selection运算符的指针,其作用是作为this指针的空间保留者。因此,static member function的类型是函数指针,而不是指向member function的指针。

使用一个member function指针,如果并不用于virtual function、多重继承、virtual base class等情况的话,其成本并不比使用一个nonmember function指针要高。

4.5 inline functions

关键词inline只是一项请求。如果在某个层次上,函数的执行成本比一般的函数调用及返回机制所带来的负荷低,那么该请求被接受,编译器就用一个表达式合理地将函数扩展开来。真正的inline函数扩展操作是在函数调用的那一点上。在inline扩展期间,每一个形式参数会被对应的实际参数所取代,inline函数中的每一个局部变量都必须被放在函数调用的一个封闭区段中,并拥有一个独一无二的名称。这会带来参数的求值操作以及临时性对象的管理。

2002-08-11

构造、解构、拷贝语意学semantics of construction, destruction, and copy

1.一般而言,class的data member应该被初始化,而且只在constructor中或其它member functions中初始化,其它任何操作都将破坏其封装性质,使其维护和修改更加困难。

2.可以定义并调用invoke一个pure virtual function,但它只能被静态调用,不能经由虚拟机制调用。每一个derived class destructor会被编译器加以扩展,静态调用每一个virtual base class以及上一层base class的destructor。因此,不管base class的virtual destructor是否声明为pure,它必须被定义。

5.1无继承情况下的对象构造

c++ standard要求编译器尽量延迟nontrivial members的实际合成操作,直到真正遇到其使用场所为止。

5.2继承体系下的对象构造

一般而言,继承体系下编译器对constructor所作的扩充操作以及次序大约如下:

ü所有virtual base class constructors必须从左到右、从深到浅被调用:如果class被列于member initialization list中,那么任何明确指定的参数都必须传递过去,否则如果class有一个default constructor,也应该调用它;class中的每一个virtual base class subobject的偏移量offset必须在执行期可被存取;如果class object是最底层most-derived的class,其constructors可能被调用,某些用以支持这个行为的机制必须被方进来。

ü以base class的声明次序调用上一层base class constructors:如果base class被列于member initialization list中,那么任何明确指定的参数都必须传递过去,否则若它有default constructor或default memberwise copy constructor,那么就调用它;如果base class是多重继承下的第二或后继的base class,那么this指针必须有所调整。

ü如果class object有virtual table pointer(s),它(们)必须被设定初值,指向适当的virtual table(s)。

ü如果有一个member没有出现在member initialization list中,但它有default constructor,调用之。

ü将member initialization list中的data members的初始化操作以members的声明次序放进constructor的函数本身。

2002-8-18

5.3对象复制语意学object copy semantics

1.只有在默认行为所导致的语意不安全或者不正确以致发生别名化aliasing或者内存泄漏memory leak时,才需要设计一个copy assignment operator。否则,程序反倒会执行得较慢。

2.如果仅仅是为了把nrv优化开关打开而提供一个copy constructor,那么就没有必要一定要提供一个copy assignment operator。

3.copy assignment operator有一个非正交情况,那就是它缺乏一个平行于member initialization list的member assignment list。调用base class的copy assignment operator示例:

point::operator = (p3d);或(*(point*)this) = p3d;或(point &)(*this) = p3d;

4.事实上,copy assignment operator在虚拟继承情况下行为不佳,需要小心设计和说明。许多编译器甚至并不尝试取得正确的语意,它们在每一个中间的copy assignment operator中调用每一个base class instance,于是造成virtual base copy assignment operator的多个实体被调用。建议尽可能不要允许一个virtual base class的拷贝操作,并不要在任何virtual base class中声明data member。

5.5解构语意学semantics of destruction

如果class没有定义destructor,那么只有在其内带的member object或base class拥有destructor时,编译器才会自动合成出一个destructor。一个由程序员定义的destructor被扩展的方式类似constructors被扩展的方式,只是顺序相反:

üdestructor的函数本体首先被执行;

ü如果class拥有member class objects,而后者拥有destructors,那么它们将以声明的相反顺序而调用;

ü如果object内带一个vptr,则现在被重新设定以指向适当base class之virtual table;

ü如果有任何直接的nonvirtual base classes拥有destructor,它们将以声明的相反顺序而调用;

ü如果有任何virtual base classes拥有destructor,而前面讨论的这个class是most-derived class,那么它们会以原先构造顺序的相反顺序被调用。

2002-8-19

执行期语意学runtime semantics

6.1对象的构造和解构

1.一般而言,constructor和destructor的安插都如你所预期。但如果一个区段或函数中有一个以上的离开点,情况就会复杂一些,destructor会放在每一个离开点之前。通常,我们要求将object尽可能放在使用它的那个程序区附近,这样做可以节省不必要的对象产生和销毁操作。

2.c++程序中所有的global objects都被放置在程序的data segment中,如果不明确指定初值,object所配置的内存内容将为0(c并不自动设定初值)。如果global object有constructor和destructor的话,我们说它需要静态的初始化和内存释放操作。

2002-8-20

3.virtual base class的subobject在每个derived class中的位置可能会变动,不能在编译时期确定。以一个derived class的pointer或reference来存取virtual base class subobject,是一种nonconstant expression,必须在执行期方可评估求值。

4.使用静态初始化的object有一些缺点。其一,无法放入try区段,任何throw操作必将触发exception handling library的默认函数terminate();其二,程序员必须为控制“需要跨越模块做静态初始化”objects的依赖顺序而产生的复杂度付出代价。建议根本就不要使用那些需要静态初始化的global objects。

5.新的c++标准要求编译单位中的static local class objects必须在相应函数第一次被调用时才被构造,而且必须以相反的次序销毁。由于这些objects是在需要时才被构造,因此编译时期无法预期其集合和顺序。为支持新标准,可能要对被产生出来的static local class objects保持一个执行期链表。

2003-8-1

6.对于对象数组定义,晚近的编译器一般会提供两个函数,分别用于处理没有virtual base class的class,以及内带virtual base class的class,它们通常被称为vec_new、vec_vnew。前者类型通常为:

void* vec_new(//初始化程序员未提供初值的连续元素

void *array,//数组起始地址若为0,则动态分配

size_t elem_size,//每一个class object的大小

int elem_count,//数组中的元素数目

void (*constructor) (void *),// class的defaultconstructor指针

void (*destructor) (void *, char)// class的destructor指针,以0填入

);如果程序员提供带有默认参数值的default constructor,编译器要做特殊处理,以传入默认参数值!

对应销毁数组的两个函数分别为vec_delete、vec_vdelete。前者类型通常为:

void* vec_delete(

void *array,//数组起始地址

size_t elem_size,//每一个class object的大小

int elem_count,//数组中的元素数目

void (*destructor) (void *, char)// class的destructor指针

);

6.2 new和delete运算符

注意区分operator new和new operator!前者负责分配内存;后者先调用前者分配内存,然后调用constructor以实施初始化。