深度探索C++对象模型四
1.virtual member functions(虚拟函数成员)
来自原书:
如果normalize()是一个virtual member function,那么下面的抵用会被转成:
ptr->normalize();
会转成:
(*ptr->vptr[1])(ptr);
- vptr:表示由编译器产生,指向虚函数表,被安插在每一个“声明有(或者继承自)一个或者多个 虚函数”的对象中。
- 1是虚函数表中的索引值,关联到normalize()函数
- 第二个ptr是指的this指针。
2.如果取一个static member function(静态成员函数)的地址的时候,获得是其在内存中的位置,也就是其地址。由于静态成员函数没有this指针,所以其地址的类型并不是一个“指向类成员函数的指针”,而是一个“非成员函数指针”。
例子静态成员函数:
unsigned int Point3d::object_count()
{
return _object_count;
}
也就是:
&Point3d::object_count();
会得到一个数值,类型是:
unsigned int(*)();
而不是:
unsigned int (Point3d::*)();
而且:静态成员函数缺少 this指针,所以差不多等同于非成员函数。
3.虚成员函数
虚函数的一般实现模型:每一个类(class)有一个虚函数表,这个虚函数表中含有虚函数的地址,每一个对象有一个虚函数指针,指向虚函数表。
识别class是否支持多态,唯一恰当的方法是看它是否有virtual function,只要class拥有virtual function,它就需要额外的执行期信息(以下两点)。
考虑ptr->z(),ptr是基类指针,z是虚函数,为了找到并调用z()的适当实体,我们需要两项信息:
1.ptr所指对象的真实类型(ptr的动态类型,在运行时决定)
2.z()实体位置(放在virtual table中)
如何实现上述信息?
在每个多态的class object身上添加两个members:
1.一个字符串或数字,表示class的类型
2.一个指针,指向某表格,表格中带有程序的虚函数的执行期地址
在c++中,虚函数可以在编译时获知,它们地址是固定不变的,放在virtual table中。为了找到virtual table,每个class object被安插一个由编译器内部产生的指针,指向该表格。为了找到函数地址,每一个虚函数被指派一个表格索引值。以上的工作由编译器完成,程序运行时要完成的是在特定的virtual table slot(记录着虚函数地址)中激活虚函数。
(编译时把每个类的虚函数地址放到该类对应的virtual table中,运行时根据指针或引用的动态类型在对应的virtual table中找到目标函数并调用)
一个class只有一个virtual table,每个table内含其对应的class object中所有active virtual function的地址。这些active virtual function包括:
1.这个类所定义的函数实体,它会改写一个可能存在的base class virtual function函数实体(覆盖)
2.继承自base class的函数实体,这是在派生类不改写virtual function时才会出现的情况
当一个类继承自另一个类时,会发生以下三种情况:
1.它可以继承base class 所声明的virtual function的函数实体,即该函数实体的地址会被拷贝到派生类的virtual table相对应的slot中
2.它可以使用自己的函数实体,该函数实体覆盖了父类的虚函数(覆盖)
3.它可以加入一个新的虚函数,这时候virtual table的尺寸会增加一个slot,而新的函数实体地址会被放进该slot之中
4.inline函数
摘自《C深入探索C++对象模型》
一般而言,处理一个inline函数,有两个阶段:
1.分析函数定义,以决定函数的“intrinsic inlin ability”(本质的inline能力)。“intrinsic”(本质的,固有的)一词在这里意指“与编译器相关”
如果函数因其复杂度,或因其建构问题,被判断不可成为inline,它会被转为一个static函数,并在“被编译模块”内产生对应的函数语义。
2.真正的inline函数扩展操作是在调用的那一点上。这会带来参数的求值操作(evaluation)以及临时性对象的管理。
同样在扩展点上,编译器将决定这个调用是否“不可为inline”。
形式参数(Formal Arguments)
一般而言,面对“会带来副作用的实际参数”,通常都需要引入临时性对象。换句话说,如果实际参数时一个常量表达式(constant expression),我们可以在替换之前先完成其求值操作(evaluations);后继的inline替换,就可以把常量直接“绑”上去。如果既不是常量表达式,也不是带有副作用的表达式,那么就直接替换之。
举例如下,假设我们有以下的简单inline函数:
inline int min(int i, int j){
return i < j ? i : j;
}
下面是三个调用操作:
inline int bar(){
int minval;
int val1 = 1024;
int val2 = 2048;
/*(1)*/ minval = min(val1, val2);
/*(2)*/ minval = min(1024, 2048);
/*(3)*/ minval = min(foo(), bar() + 1);
return minval;
}
标识为(1)的那一行会被扩展为:
minval = val1 < val2 ? val1 : val2;
标识为(2)的那一行直接拥抱常量:
minval = 1024;
标识为(3)的那一行则引发参数的副作用,它需要导入一个临时对象,以避免重复求值(multiple evaluations)
int t1;
int t2;
minval = (t1 = foo()), (t2 = bar() + 1),
t1 < t2 ? t1 : t2;
局部变量(Local Variables)
一般而言,inline函数中的每一个局部变量都必须被放在函数调用的一个封闭区段中,拥有一个独一无二的名称。如果inline函数以单一表达式(expression)扩展多次,则每次扩展都需要自己的一组局部变量。如果inline函数以分离的多个式子(discrete statements)被扩展多次,那么只需一组局部变量,就可以重复使用(译注:因为它们被放在一个封闭区段中,有自己的scope)