一、两个基本概念
我们已经讨论过virtual function的一般实现模型:每个class 有一个virtual table,内含该class中有作用的virtual function的地址,然后每一个object有一个vptr,指向virtual table的所在。
在C++中,多态表示“以一个public base class的指针(或引用),寻址出一个derived class object“。如:
// Point2d : Point
Point* ptr_point = new Point2d();
Point &ref_point = new Point2d();
二、单一继承下的Virtual Functions
这里的单一继承指的是一般的单一继承,不是虚拟继承。在我们的Point class继承体系中:
// Point
class Point
{
public:
virtual ~Point();
virtual Point& mult(float) = 0;
// ... 其他操作
float x() const { return _x; }
virtual float y() const { return 0; }
virtual float z() const { return 0; }
// ...
protected:
Point(float x = 0.0);
float _x;
};
// Point2d : Point
class Point2d : public Point
{
public:
Point2d(float x = 0.0, float y = 0.0)
:Point(x), _y(y) {}
~Point2d();
// 重写base class virtual functions
Point2d& mult(float);
float y() const { return _y; }
// ... 其他操作
protected:
float _y;
};
// Point3d : Point2d
class Point3d : public Point2d
{
public:
Point3d(float x = 0.0, float y = 0.0, float z = 0.0)
:Point2d(x, y), _z(z) {}
~Point3d();
// 重写base class virtual functions
Point3d& mult(float);
float z() const { return _z; }
// ... 其他操作
protected:
float _z;
};
Point、Point2d和Point3d的内存布局和其virtual tables如下图所示:
virtual destructor被指派slot 1,而mult()被指派slot 2,此例并没有mult()的函数定义(因为它是一个纯虚函数),所以pure_virtual_called()的函数地址会被放在slot 2中。如果该函数被意外的调用,通常的操作是结束掉这个程序。y()被只拍到slot 3,而z()被指派到slot 4。x()的slot是多少?并没有,因为它非virtual function。这里,内存布局只有数据成员和虚函数的虚表指针,并没有其他成员函数。如果该类中有static member,那么也不会出现在该内存布局中,因为static member存在于data segment,不在class object中。
成员函数可以被看作是类作用域的全局函数,不在对象分配的空间里,只有虚函数才会在类对象里有一个指针,存放虚函数的地址等相关信息。成员函数的地址,编译期就已确定,并静态绑定或动态的绑定在对应的对象上。对象调用成员函数时,编译器可以确定这些函数的地址,并通过传入this指针和其他参数,完成函数的调用,所以类中就没有必要存储成员函数的信息。
三、虚拟继承下的Virtual Functions
考虑下面的virtual base class派生体系,从Point2d派生出Point3d:
class Point2d
{
public:
Point2d(float x = 0.0, float y = 0.0);
virtual ~Point2d();
virtual void mumble();
virtual float z();
// ...
protected:
float _x, _y;
};
// Point3d : public virtual Point2d
class Point3d : public virtual Point2d
{
public:
Point3d(float x = 0.0, float y = 0.0, float z = 0.0);
~Point3d();
float z();
protected:
float _z;
};
虚继承下的内存布局如下图:
到这里我想上图已经看得有点神经错乱了,Lippman在P169页这样写道:
当一个 virtual base class从另一个 virtual base class 派生而来,并且两者都支持 virtual functions 和nonstatic data members 时,编译器对于 virtual base cIass 的 支持简直就像进了迷宫一样。虽然我手上有一整柜带有答案的例程,并且有一个以上的算法可以决定适当的 offsets以及各种调整,但这些素材实在太过诡异迷离,不适合在此处讨论!我的建议是,不要在一个 virtual base class中声明 nonstatic data members。 如果这么做,你会距离复杂的深渊愈来愈近,终不可拔。