在第一篇文章中我们谈到member function有三种:nonstatic、static和virtual。前面两种并不是我们今天的主角,我们要学习的是virtual member functions。
nonstatic member functions
C++的设计准则之一就是:nonstatic member function至少必须和一般的nonmember function有相同的效率,实际上member function被编译器内化为nonmember的形式。member function的调用必须经由一个class object才能操作,这点与static mmber function不同。
static member functions
一个static member function的主要特性是它没有this指针,并且由此衍生出来的其他特性有:
1、它不能够直接存取class中的nonstatic member;
2、它不能够被声明为const、volatile或virtual;
3、它的调用不需要经由class object(虽然大部分时候它是这么被调用的)。
// 如果Point3d::normalize()是一个static member function
obj.normalize();
ptr->normalize();
// 那么以上两个调用操作,将会被转换为一般的nonmember函数的调用。
单一继承下的virtual functions
在之前的文章中,我们或多或少的了解了virtual的一般实现模:每一个class有一个vtbl,内含class的virtual functions的地址,每个class object有一个vptr,指向vtbl。
我们来看一个例子:
class Point {
public:
virtual ~Point();
virtual Point& mult(float) = 0;
// ...
float x() { return _x; }
virtual float y() const { return 0.0F; }
virtual float z() const { return 0.0F; }
// ...
protected:
Point(float x = 0.0F);
float _x;
};
class Point2d : public Point {
public:
Point2d(float x = 0.0F, float y = 0.0F) : Point(x), _y(y) {}
~Point2d();
Point2d& mult(float);
float y() const { return _y; }
protected:
float _y;
};
显然这是一个单一继承的例子,很简单。那么就有一个问题来了,当我们调用某个virtual member function譬如说ptr->y()的时候,那我们调用的是classPointobject的y()还是classPoint2dobject的y()呢?也就是说,y()是一个virtual member function,我们知道RTTI的特性就是某些信息只有在执行期才能确定,而在编译期是不能确定的。
显然,不管编译期还是执行期,若我们要正确的执行y()的实例,那么整个过程中需要知道:
1、ptr所指对象的真实类型;
2、y()实例的位置。
根据前面几节对象模型学习,我们知道在C++对象模型中,在编译期编译期会为我们构建两个东西:vptr和vtbl,在程序执行时表格的大小和内容是不会发生改变的。由于要执行y()函数,可以由两个步骤来完成这项任务(即vptr和vtbl的构建):
1、为了找到表格,每一个class object被安插了一个由编译器内部产生的指针,指向该表格;
2、为了找到该函数地址,每一个virtual function被指派一个表格索引值。
当然,以上的工作都是有编译器在编译期完成的,那么在执行期所要做的就是,只在特性的virtual table slot中激活virtual function。
根据上面的对象模型,我们在编译期就构建了vptr和vtbl,而且已经知道了某个函数的索引值,因此唯一一个在执行期才能知道的东西就是:slot所指的到底是哪一个函数实例,当然执行期ptr的具体指向会为我们解决这个问题。
class Point3d : public Point2d {
public:
Point3d(float x = 0.0F, float y = 0.0F, float z = 0.0F) : Point2d(x, y), _z(z) {}
~Point3d();
Point3d& mult(float);
float z() const { return _z; }
// ...
protected:
float _z;
};
在单一继承中,virtual function机制的行为非常良好的,不但有效率而且很容易被塑模出来。
多重继承下的virtual functions
对于多重继承,我们还是直接来看一个例子。
class Base1 {
public:
Base1();
virtual ~Base1();
virtual void speakClearly();
virtual Base1* clone() const;
protected:
float data_Base1;
};
class Base2 {
public:
Base2();
virtual ~Base2();
virtual void mumble();
virtual Base1* clone() const;
protected:
float data_Base2;
};
class Derived : public Base1, public Base2 {
public:
Derived();
virtual ~Derived();
virtual Derived* clone() const;
protected:
float data_Derived;
};
在多重继承之下,对象模型的机制是:一个derived table slot内含n-1个额外的virtual tables,n表示其上一层base classes的个数(因此,单一继承将不会有额外的virtual tables)。对于本例的Derived而言,会有两个virtual tables被编译器产生出来:
1、一个主要实例,与Base1(最左端base class)共享;
2、一个次要实例,与Base2(第二个base class)有关。
针对每一个virtual tables,Derived对象中有对应的vptr,具体对象模型如下:
虚拟继承下的virtual functions
在之前的文章中,我们大概也见识到了虚拟继承的特殊性,class subobject作为共享部分放在最后面,而在vtpr所指向的vtbl中的开头添加了表示offset的项。下面还是来看一个现实例子。
class Point2d {
public:
Point2d(float = 0.0F, float = 0.0F);
virtual ~Point2d();
virtual void mumble();
virtual float z();
// ...
protected:
float _x;
float _y;
};
class Point3d : public virtual Point2d {
public:
Point3d(float = 0.0F, float = 0.0F, float = 0.0F);
virtual ~Point3d();
virtual float z();
// ...
protected:
float _z;
};
其对象模型如下:
这样一来我们就清楚了虚拟继承下的对象模型。
有一点建议就是:不要再一个virtual base class中声明nonstatic data members。
最后,传一个在学习《深度探索C++对象模型》过程中记录的一些模型图,在这里:http://download.csdn.net/detail/hujingshuang/9663843。
参考资料:
[1] 深度探索C++对象模型,[美]Stanley B. Lippman著,侯捷译;