深度探索C++对象模型四

深度探索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)

发布了197 篇原创文章 · 获赞 68 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/Travelerwz/article/details/98339321