Impossible is nothing  
  爱过知情重醉过知酒浓   花开花谢终是空   缘份不停留像春风来又走   女人如花花似梦
公告
日历
<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567
统计
  • 随笔 - 8
  • 文章 - 91
  • 评论 - 16
  • 引用 - 0

导航

常用链接

留言簿(4)

随笔分类(4)

随笔档案(8)

文章分类(77)

文章档案(91)

相册

搜索

  •  

最新评论

阅读排行榜

评论排行榜

 

Stanley B. Lippman

 

屋檐下的水滴 --读书笔记系列

http://blog.csdn.net/dwater

 

《深度探索 C++ 对象模型》读书笔记                                                                                http://blog.csdn.net/dwater

Stanley B.Lippman

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 <class class_type, class data_type1, class data_type2>

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 default constructor 指针

           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 以实施初始化。

 

 

《深度探索 C++ 对象模型》读书笔记      

posted on 2006-06-03 17:42 笑笑生 阅读(685) 评论(1)  编辑 收藏 引用 所属分类: C++语言
评论:

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


 
Copyright © 笑笑生 Powered by: 博客园 模板提供:沪江博客