由于我对这方面不是很明白,所以只好实验来让我更好的理解,将结果记录在这里吧~

  理论

  我们采用第一个定义,也是说被virtual所修饰的事物或现象在本质上是存在的,但是没有直观的形式表现,无法直接描述或定义,需要通过其他的间接方式或手段才能够体现出其实际上的效果。

  那么在C++中是采用了这个词意,不可以在语言模型中直接调用或体现的,但是确实是存在可以被间接的方式进行调用或体现的。比如:虚函数必须要通过一种间接的运行时(而不是编译时)机制才能够激活(调用)的函数,而虚继承也是必须在运行时才能够进行定位访问的一种体制。存在,但间接。其中关键在于存在、间接和共享这三种特征。

  对于虚函数而言,这三个特征是很好理解的,间接性表明了他必须在运行时根据实际的对象来完成函数寻址,共享性表象在基类会共享被子类重载后的虚函数,其实指向相同的函数入口。

  对于虚继承而言,这三个特征如何理解呢?存在即表示虚继承体系和虚基类确实存在,间接性表明了在访问虚基类的成员时同样也必须通过某种间接机制来完成(下面模型中会讲到),共享性表象在虚基类会在虚继承体系中被共享,而不会出现多份拷贝。

  模型

  为了实现上面所说的三种语义含义,在考虑对象的实现模型(也是内存模型)时很自然了。在C++中对象实际上是一个连续的地址空间的语义代表,我们来分析虚继承下的内存模型。

  3.1 存在

  也是说在对象内存中必须要包含虚基类的完整子对象,以便能够完成通过地址完成对象的标识。那么至于虚基类的子对象会存放在对象的那个位置(头、中间、尾部)则由各个编译器选择,没有差别。(在VC8中无论虚基类被声明在什么位置,虚基类的子对象都会被放置在对象内存的尾部)

  3.2 间接

  间接性表明了在直接虚基承子类中一定包含了某种指针(偏移或表格)来完成通过子类访问虚基类子对象(或成员)的间接手段(因为虚基类子对象是共享的,没有确定关系),至于采用何种手段由编译器选择。(在VC8中在子类中放置了一个虚基类指针vbc,该指针指向虚函数表中的一个slot,该slot中存放则虚基类子对象的偏移量的负值,实际上是个以补码表示的int类型的值,在计算虚基类子对象首地址时,需要将该偏移量取值相加,这个主要是为了和虚表中只能存放虚函数地址这一要求相区别,因为地址是源码表示的无符号int类型的值)

  3.3 共享

  共享表明了在对象的内存空间中仅仅能够包含一份虚基类的子对象,并且通过某种间接的机制来完成共享的引用关系。在介绍完整个内容后会附上测试代码,体现这些内容。

  实验:首先按照常理,只在终的继承里面加了虚继承,这种情况下多了8个字节,其中4个字节是派生类自己的虚函数表。还有4个字节是什么?虚继承表?

class Base1 {
private:
 int base1;
public:
 virtual void f() { cout << "Base1::f" << endl; }
 virtual void g() { cout << "Base1::g" << endl; }
 virtual void h() { cout << "Base1::h" << endl; }
};
class Base2 {
private:
 int base2;
public:
 virtual void f() { cout << "Base2::f" << endl; }
 virtual void g() { cout << "Base2::g" << endl; }
 virtual void h() { cout << "Base2::h" << endl; }
};
class Base3 {
private:
 int base3;
public:
 virtual void f() { cout << "Base3::f" << endl; }
 virtual void g() { cout << "Base3::g" << endl; }
 virtual void h() { cout << "Base3::h" << endl; }
};
class Derive : public virtual Base1, public virtual Base2, public virtual Base3 {
private:
 int derive;
public:
 virtual void f() { cout << "Derive::f" << endl; }
 virtual void g1() { cout << "Derive::g1" << endl; }
};
int main()
{
 Derive d;
 cout<<sizeof(d)<<endl;
 return 0;
}