深入探索C++对象模型 之 Function语意学

类别:编程语言 点击:0 评论:0 推荐:

名称的特殊处理(Name Mangling)

如member:

class bar{public: int ival; …}

class foo :public class bar {public: int ival; …}

经处理后:

class foo{

public:

int ival_3bar;

int ival_4foo;

}

如function:

class point {

public:

void x (float newX);

float x();

}

经处理后:

class Point {

public:

void x_5PointFf (float newX);

float x_5PointFv ();

}

把参数和函数名称编码在一起,编译器于是在不同的被编译模块之间达成了一种有限形式的类型检验(“返回类型”的声明错误没办法检查出来)。

 

Nonstatic Member Functions (非静态成员函数)

需经过内部转化,步骤:

1. 改写函数原型以安插一个额外的参数到member function 中,用以提供一个存取管道,使class object 得以调用该函数。该额外参数被称为this 指针:如

Point3d Point3d::magnitude(Point3d *const this)

如果member function 是const , 则变成:

Point3d Point3d::magnitude(const Point3d *const this)

2.将每一个“对nonstatic data member 的存取操作”改为经由this 指针来存取。

3.将member function 重新写成一个外部函数。对函数名称进行“mangling”处理,使它在程序中成为独一无二的语汇。

而函数的调用操作也都必须转换。

如:obj.magnitude() 变成了:magnitude_7Point3dFv(&obj);

 

Static Member Function (静态成员函数)

如:obj.normalize();ptr->normalize();将都被转换为一般的nonmember 函数调用,

象这样:normalize()_7Point3dSFv();

Static member functions 的主要特性就是它没有this指针,次要特性统统根源于其主要特性:

1. 它不能直接存取其class中的nonstatic members。

2. 它不能够被声明为const,volatile 或 virtual。

3. 它不需要经由class object 才被调用——虽然大部分时候它是这样被调用  的!

如果class object 是因为某个表达式而获得的,这个表达式仍然需要被评估求值;

如:if ( foo ().objcet_count () > 1) …

被转换为:(void) foo ();    if ( Point3d::object_count() > 1) …

如果取一个static member function 的地址,获得的将是其在内存中的位置,也就是其地址。由于static member function 没有 this 指针,所以其地址的类型并不是一个“指向class member function 的指针”,而是一个“nonmember 函数指针”,也就是说:

&Point3d::object_count();会得到一个数值,类型是:unsigned int (*) ();

而不是:unsigned int (Point3d::*) ();

 

Virtual Member Function (虚拟成员函数)

如:ptr->normalize();将被内部转化为:(*ptr->vptr[1])(this);

明确的调用操作(explicitly invocation)会压制虚拟机制,其决议方式和nonstatic member function一样。如:register float mag = Point3d::magnitude();//magnitude是virtual function

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

识别一个class 是否支持多态,唯一适当的方法就是看看它是否有任何virtual function。

有这样的调用:ptr->z();

为了在执行期调用正确需要知道两点:

1. ptr所指对象的真实类型。这可使我们选择正确的z()实体;

2. z() 实体位置,以便我能够调用它。

实现上,可以在每一个多态的class object 身上增加两个members:

1. 一个字符串或数字,表示class 的类型;

2. 一个指针,指向某表格,表格中带有程序的virtual functions 的执行期地址。

为了在执行期找到virtual function 的地址,需要两个步骤:

1. 为了找到表格,每一个class object 被安插上一个由编译器内部产生的指针,指向该表格。

2. 为了找到函数地址,每一个virtual function 被指派一个表格索引值。

 

单重继承下的virtual functions:

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

1.这个class 所定义的函数实体。它会改写(overriding)一个可能存在的base class virtual function 函数实体。

2.继承自base class 的函数实体。这是在derived class 决定不改写virtual function 时才会出现的情况。

3.一个pure_virtual_called() 函数实体,它既可以扮演 pure virtual function 的空间保卫者角色,也可以当做执行期异常处理函数(有时会用到)。

调用pure virtual function ,通常会结束掉程序。

 

多重继承下的virtual functions:

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

如:Base2 *Pbase2 = new Derived;新的Derived 对象的地址必须调整,以指向其Base2 subobject.编译时期会产生以下的码:

//转移以支持第二个base class

Derived *temp = new Derived;

Base2 *Pbase2 = temp ? temp + sizeof (Base1 ) : 0;

当程序员要删除Pbase2所指的对象时:指针必须被再一次调整,以求再一次指向Derived 对象的起始处。

在多重继承之下,一个derived class 内含n-1 个额外的virtual tables,n 表示其上一层 base classes 的数目。

1.一个主要实体,与Base1共享(Vtbl_Derived).

2.一个次要实体,与Base2 有关(Vtbl_Base2_Derived).

允许一个virtual function 的返回值类型有所变化,可能是base type,也可能是 publicly derived type。

 

虚拟继承下的virtual function:

有个建议是,不要在一个virtual base class 中声明nonstatic data members。

 

指向member function 的指针(Pointer-to-Member Functions)

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

取一个nonstatic member function 的地址,如果该函数是nonvirtual ,则得到的结果是它在内存中真正的地址。这个值也是不完全的,它也需要被绑定于某个class object 的地址上,才能够通过它调用该函数。所有的nonstatic member functions 都需要对象的地址(以参数this 指出)。

一个指向 member function 的指针,如:

double (Point::*pmf) ()=&Point::x;

x 可以是virtual 或者是nonvirtual 的。但是被解释的结果不一样:

((( int ) pmf ) & ~127 ) ? ( *pmf ) ( ptr ) : ( *ptr->vptr[ ( int )pmf ] ( ptr ) )

 

Inline Functions(内联函数)

处理inline 函数,有两个阶段:

1. 分析函数定义,以决定函数的“intrinsic inline ability”(本质的inline 能力)。“intrinsic”(本质的,固有的)一词在这里意指“与编译器有关”。如果函数因其复杂度,或因其建构问题,被判断不可成为inline,它会被转为一个static 函数。

2. 真正的inline 函数扩展操作是在调用的那一点上,这会带来参数的求值操作(evaluation)以及临时性对象的管理。

形式参数:

一般而言,面对“会带来副作用的实际参数”,通常都需要引入临时性对象。

即如果实际参数是一个常量表达式,可以在替换之前先完成求值操作;后继inline 替换,就可以把常量直接“绑”上去。如果既不是个常量表达式,也不是个带有副作用的表达式,那么就直接代换之。

局部变量:

如果inline 函数以单一表达式扩展多次,那么每次扩展都需要自己的一组局部变量。如果inline 函数以分离的多个式子被扩展多次,那么只需一组局部变量,就可以重复使用(因为它们被放在一个封闭区段中,有自己的scope)。

如:单一表达式:
minval = min ( val1, val2 ) + min ( foo (), foo () + 1);

分离的多个表达式:
minval_1 = min ( val1, val2 );
minval_2 = min ( val3, val4 );

Inline 函数对于封装提供了一种必要的支持,可以有效存取封装于class 中的nonpublic 数据。它同时也是C 程序中大量使用的#define (前置处理宏)的一个安全代替品——特别是如果宏中的参数有副作用的话。然而一个inline 函数如果被调用太多次的话,会产生大量的扩展码,使程序的大小暴涨。

本文地址:http://com.8s8s.com/it/it24304.htm