说明:
<u>不是很清楚的点</u>,用下划线。
解答,用斜体;
重点,用粗体加粗;
第四章 Function 语意学
4.1 Member的各种调用方式
1. Nonstatic Member Functions
实际上编译器是将member function被内化为nonmember的形式,经过下面转化步骤:
- 给函数添加额外参数——this;
- 将对每一个nonstaitc data member的存取操作改为this指针来存取;
- 将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:
- 一个字符串或数字,表示class的类型;
- 一个指针vptr,指向某一个表格,表格中持有程序的虚函数们的执行期地址;
- 每一个虚函数都被指派一个表格索引值;
那么,表格中的虚函数们地址如何被购建起来?
在C++中,虚函数们可经由其class object被调用,可以在编译时期获知。此外,这一组地址是固定不变的,执行期不可能新增或替换之。由于程序执行时,表格的大小和内容都不会改变,所以其建构和存取皆可以由编译器完全掌控。不需要执行期的任何介入。**
而执行期要做的,只是在特定的虚函数表slot中激活虚函数。
每一个class 只会有一个virtual table,每一个table 含有对应的class object中所有active virtual functions 函数实体地址。这些active virtual function 包括:
- 这个class 所定义的函数实体(包括改写一个可能存在的base class virtual function函数实体)。
- 继承自base class 的函数实体(不被derived class改写)
- 一个pure_virtual_called()。
继承过程中,virtual table的三种可能性:
- 继承base class 所声明的virtual functions的函数实体。正确地说,是该函数实体的地址会被拷贝到derived class的virtual table相对应的slot之中。
- 使用自己的函数实体。这表示它自己的函数实体地址必须放在对应的slot之中。
- 可以加入一个新的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
表示其上一层基类的个数(因此,单一继承将不会有额外的虚函数表)。【若是双重继承,那么就会有一个与派生类共享的虚函数表】
如下图:
【注】
- 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 );
因此要以分离的多个式子被调用。