您的位置:首页 > 编程语言 > C语言/C++

【C++】深度探索C++对象模型之Function语意学

2016-01-01 20:26 691 查看

一、Member的各种调用方式

1. Nonstatic Member Functions

首先给出nonstatic member function 和 nonmember function

float magnitude3d( const Point *_this ) { ... } // nonmember function
float Point3d::magnitude3d() const { ... } // nonstatic member function
对于C++中class中的非静态成员函数,都会转化成对等的 “ nonmember 函数实例 ”, 也就是

float magnitude3d( const Point3d* _this ) {
return sqrt( _this->x * _this->x +
_this->y * _this->y +
_this->z * _this->z );
}
上式就是通过对 nonstatic member function

float Point3d::magnitude3d() const { ... } // nonstatic member function
改写成nonmember function的过程。转化步骤如下:

改写函数的原型以安插一个额外的参数到member function中,此例为_this指针。以及const nonstatic member
对每个“nonstatic data member的存取操作” 改成this指针来存取,也就是 _this->x 等
将member function重新写成一个外部函数。 也就是对上式改写成类似

extern magnitude__7Point3dFv( register Point3d *const this );

调用操作抑由 obj.magnitude() 改写成 magnitude__7Point3dFv( &obj );
而 ptr->magnitude() 改写成 magnitude__7Point3dFv(ptr) ;
上述经过named return value函数的内部转化还没被改写成
void normalize_7Point3dFv( register const point3d *const this, Point3d &__result )
{
register float mag = this->magnitude();
_result.Point3d::Point3d( this->x_mag, this->y_mag, this->z_mag );
return;
}


名称的特殊处理
对于member的名称,一般前面会加上class名称,以形成独一无二的命名。 也就是mangling 手法来提供独一无二的名称。
Name mangling 就是通过内部的编码对函数名称申明唯一的实例。
如果名称不一致,会导致编译器在链接时期因无法决议而失败,也就是确保类型安全的链接行为。

2. virtual Member funtions

如果normalize()是一个virtual member function,那么以下调用
ptr->normalize();
会被转换为:
(* ptr->vptr[1])( ptr );


vptr即指向virtual table的指针
1是virtual table slot的索引值,也就是指向virtual table上的哪个函数的下标,这里是关联到normalize
第二个ptr表示this 指针。

3. static member function

如果 Point3d::normalize()是一个static member function, 那么以下调用:
obj.normalize();
ptr->normalize();
一般都会转化为以下nonmember函数的调用,也就是
// obj.normalize();
normalize_7Point3dSFv();
// ptr->normalize();
normalize_7Point3dSfv();


在引入static member function之前,C++要求所有的成员函数必须由class的object来调用
而实际上,只有当一个或者多个nonstatic data member在member function中被直接存取时,才需要class object。这个this指针绑定着object对应的member之上。
因此,如果class的设计者,把static data member声明为nonpublic,那么就必须提供一个或多个member function来存取该member。因此可以不靠class object来存取一个static member,但其存取函数却得绑定于一个class object之上。

如果希望支持“没有 class object 存在”,那么程序的解决之道是把0强制转化为一个class指针,因而提供一个this指针实例。
object_count( (Point3d*) 0);


static member function 的主要特性是没有this 指针,其余特性如下:

它不能直接存取其class中的nonstatic members
它不能被申明为const , volatile 或者 virtual
它不需要经由class object才被调用。

【如果取一个static member function的地址,获得的讲师其在内存中的位置,也就是其地址。】
【由于static member function没有this指针,所以其地址类型并不是一个“指向class member function的指针”,而是一个“nonmember 函数指针”。】
也就是说调用
&Point3d::object_count();
获取的是
unsigned int(*) ();
而不是
unsigned int( Point3d::* ) ();


因此static member function的好处是成为一个callback函数,也可以应用于线程threads之上。

二、virtual member function

在C++中,多态(polymorphism)表示:以一个public base class的指针(或reference),寻址出一个derived
class object。
比如:
Point *ptr;
ptr = new Point2d;   //寻址出一个Point2d的对象
ptr = new Point3d;   //寻址出一个Point3d的对象


欲鉴定哪些classes展现多态特性,我们需要额外的执行期信息,因此识别一个class是否支持多态的唯一适当方法,是看看是否含有virtual function。

virtual function的一般实现模型:每一个class有一个virtual table,内含class之中有作用的virtual function的地址。然后每个object都有一个vptr,指向virtual table的所在。

为了支持virtual function的机制,必须首先对于多态对象有某种形式的“执行期类型判断法(runtime type resolution)”。

假如有例子如下:

Point *ptr1, *ptr2;
ptr1 = new Point2D;
ptr2 = new Point3D;
ptr1->z();




其中z()是一个virtual function, 为了在执行期调用正确的z(), 需要知道

ptr所指对象的真实类型
z()实例的位置

所以在实现上,我们需要为每一个多态的class object增加两个members

一个字符串或数字,表示class的类型
一个指针,指向某表格,表格中有程序的virtual function的执行期的地址。

注意,上述所说的地址在程序执行之后,表格的大小和内容都不会改变。

如何寻址

为了找到表格,每一个class object被安插了一个由编译器内部产生的指针,指向该表格
为了找到函数地址,每一个virtual function被指派一个表格索引值。

每一个class只会有一个virtual tablevirtual table的存储内容

每一个table内含其对应的class object中所有active virtual function函数实例的地址, 包括:

class所定义的函数实例,overriding可能存在的base class virtual function的函数实例
继承自base class的函数实例。即derived class决定不改写virtual function时才出现的情况
pure_virtual_called()函数实例,扮演pure virtual function的空间保卫者角色,也可以当做执行期异常处理函数。

每一个virtual function都被指派一个固定的索引值也就是slot,这是为了索引在上述所说的active virtual function在virtual table中的编号或者地址。

在单一继承中,一般每个上述的active virtual function的实例在virtual table中的布局的slot是一致的。所以对于调用

ptr->z();
以上信息足够使得编译器将调用转化为:
(*ptr->vptr[ 4 ])(ptr);
唯一在执行期才能够知道的东西是:slot 4到底是指向哪个z()的实例。

1. 多重继承下的virtual function

假设我们有以下的继承关系:
class Derived : public Base1, public Base2;
其中三个类中分别有虚析构函数,成员函数mumble(), 以及对应对象的clone()函数。如Base2
virtual ~Base2();
virtual void mumble();
virtual Base2 *clone() const;
当执行下述语句时:
Base2 *pbase2 = new Derived;
此时新的Derived对象的地址必须调整以指向其Base2 subobject。编译器会产生以下代码:
Derived *temp = new Derived;
Base2 *pbase2 = temp ? temp + sizeof(Base1) : 0;
而当程序删除pbase2所指的对象时,如
delete pbase2;
此时指针将会再次被调整,以指向Derived对象的起始处。上述的offset的加法在编译时期不能直接设定,因此pbase2所指的真正对象只有在执行期才会确定。

在多重继承下,一个derived class内含n-1个额外的virtual table,n表示其上一层base classes的个数。派生类的虚函数表是与基类共享的,所以有几个基类,就会有几个虚函数表,其中第一个虚函数表是派生类最最左的基类共享的

问题:符号名称的链接很慢,如何解决?
--> 为了调节链接器的效率,就把多个virtual table连锁成一个。指向次要表格的指针由主要表格的指针加上一个offset-->因此最终每个class只有一个具名的vtable。

2. 虚拟继承下的virtual Functions

假如:
class Point3d : public virtual Point2d
则面对于Point3d的virtual function的布局而言。
Virtual Table Point3d的第一个值是一个offset to virtual base,指向Point2d subobject。 而在Point2d subobject中则有另一个vptr指向Virtual table Point2d subobject of Point3d。



参考
深度探索C++对象模型,侯捷, 第四章 Function语意学
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: