深度探索C++对象模型-第四章

说明:

​ <u>不是很清楚的点</u>,用下划线。

解答,用斜体;

重点,用粗体加粗;

第四章 Function 语意学

4.1 Member的各种调用方式

1. Nonstatic Member Functions

实际上编译器是将member function被内化为nonmember的形式,经过下面转化步骤:

  1. 给函数添加额外参数——this;
  2. 将对每一个nonstaitc data member的存取操作改为this指针来存取;
  3. 将member function 重写成一个外部函数。对函数名采用mangling 处理,使之成为独一无二的语汇;

2. Virtual Member Functions(虚成员函数)

ptr->f(); //f()为虚成员函数

内部转化为

(*ptr->vptr[1])(ptr);

其中:

  • vptr表示编译器产生的指针,指向virtual table。它被安插在每一个声明有(或继承自)一个或多个virtual functions 的class object 中。
  • 1 是virtual table slot的索引值,关联到normalize()函数。
  • 第二个ptr表示this指针。

3. Static Member Functions (静态成员函数)

Static Member Functions的主要特性就是它没有this指针

次要特性:

  • 不能被声明为const、volatile、virtual;
  • 不能够直接存取其class中的非静态数据成员;

Static Member Functions由于缺乏this指针,因此差不多等同于非成员函数

如果取一个static member function 的地址,获得的是其在内存的位置(也就是地址),而不是一个指向“class member function”的指针,如下:

&Point::count();

会得到一个数值,类型是:

unsigned int(*)();

而不是:

unsigned int(Point::*)();

4.2 Virtual Member Functions

C++中,多态表示以“一个public base class 的指针(或reference),寻址出一个derived class object”。

为了在执行期调用正确的虚函数,在编译时期:

  • 可以在每一个多态的class object身上增加两个members:
  1. 一个字符串或数字,表示class的类型;
  2. 一个指针vptr,指向某一个表格,表格中持有程序的虚函数们的执行期地址;
  • 每一个虚函数都被指派一个表格索引值;

那么,表格中的虚函数们地址如何被购建起来?

在C++中,虚函数们可经由其class object被调用,可以在编译时期获知。此外,这一组地址是固定不变的,执行期不可能新增或替换之。由于程序执行时,表格的大小和内容都不会改变,所以其建构和存取皆可以由编译器完全掌控。不需要执行期的任何介入。**

执行期要做的,只是在特定的虚函数表slot中激活虚函数。


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

  1. 这个class 所定义的函数实体(包括改写一个可能存在的base class virtual function函数实体)。
  2. 继承自base class 的函数实体(不被derived class改写)
  3. 一个pure_virtual_called()。

继承过程中,virtual table的三种可能性:

  1. 继承base class 所声明的virtual functions的函数实体。正确地说,是该函数实体的地址会被拷贝到derived class的virtual table相对应的slot之中。
  2. 使用自己的函数实体。这表示它自己的函数实体地址必须放在对应的slot之中。
  3. 可以加入一个新的virtual function。这时候virtual table 的尺寸会增大一个slot放进这个函数实体地址。

编译时期设定virtual function的调用:

ptr->z();

  • 一般而言,我并不知道ptr 所指对象的真正类型。然而可以经由ptr 可以存取到该对象的virtual table。
  • 虽然我不知道哪个Z()函数实体被调用,但知道每一个Z()函数地址都被放置某一个slot(如slot 4)的索引。

这样我们就可以将

ptr->z();

转化为:

(*ptr->vptr[4])(ptr);

唯一一个在执行期才能知道的东西是:slot4所指的到底是哪一个class object的z()函数实体。

多重继承下的 Virtual Functions

在多重继承中支持virtual functions,其复杂度围绕在第二个及其后面的base class 上,以及“必须在执行期调整this 指针”这一点。

一般规则是,经由指向“第二或后继base class 的指针”来调用derived class virtual function。调用操作连带的“必要的this指针调整”操作,必须在执行期完成。

在多重继承下,一个派生类内含n-1个额外的虚函数表,n表示其上一层基类的个数(因此,单一继承将不会有额外的虚函数表)。【若是双重继承,那么就会有一个与派生类共享的虚函数表

如下图:

4-2.png

【注】

  • base1的虚函数表是主要表格,base2的是次要表格;
  • 函数后面有加*,表示这不是真实的函数实例,是需要加this指针指向真正的函数实例。

4.3 函数的效能

non-member、static member或non-static member函数都被转换为完全相同形式,所以三者效率完全相同。

4.4 指向Member Function的指针

取一个non-static data member的地址,得到的结果是该member在class 布局中的bytes位置(再加1),所以它需要绑定于某个class object的地址上,才能够被存取。

取一个non-static member function的地址,如果该函数是non-virtual,则得到的是内存的真正地址,然后这个值也是不完全的,也需要绑定于某个class object的地址上,才能够调用函数。(所有的non-static member function都需要对象的地址,以参数this指出)

支持“指向Virtual Member Function”之指针

对于一个virtual function,其地址在编译时期是未知的,所能知道的仅是virtual function在其相关之virtual table的索引值,也就是说,对于一个virtual member function 取其地址,所能获得的只是一个索引值。

4.5 Inline Function

形参:

  • 传入参数,直接替换;
  • 传入常量,连替换都省了,直接变成常量;
  • 传入函数运行结果,则需要导入临时变量,以避免重复求值;

局部变量:

一般而言,inline函数中的每一个局部变量都必须被放在函数调用的一个封闭区段中,拥有一个独一无二的名称。

如果一次性调用N次,就会出现N个临时变量……程序的体积会暴增,如下:

minval = min( val1, val2 ) + min( foo(), foo()+1 );

因此要以分离的多个式子被调用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容