C++虚函数表剖析 ②

多重继承(无虚函数覆盖)

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

class 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
{
public:
    virtual void f() { cout << "Base2::f" << endl; }  //虚函数定义
    virtual void g() { cout << "Base2::g" << endl; }
    virtual void h() { cout << "Base2::h" << endl; }
};

class 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 Base1, public Base2, public Base3 //多继承的情况——无虚继承覆盖
{
public:
    virtual void f1() { cout << "Derive::f1" << endl; } //虚函数定义
    virtual void g1() { cout << "Derive::g1" << endl; }
};

图解:

对于子类实例中的虚函数表,是下面这个样子:

我们可以看到:
1) 每个父类都有自己的虚表。
2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

多重继承(有虚函数覆盖)

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。

注意:子类只overwrite了父类的f()函数,而还有一个是自己的函数(我们这样做的目的是为了用g1()作为一个标记来标明子类的虚函数表)。而且每个类中都有一个自己的成员变量:

类继承的源代码如下所示:父类的成员初始为10,20,30,子类的为100。

class Base1 {
public:
    int ibase1;
    Base1() :ibase1(10) {}
    virtual void f() { cout << "Base1::f()" << endl; }
    virtual void g() { cout << "Base1::g()" << endl; }
    virtual void h() { cout << "Base1::h()" << endl; }

};

class Base2 {
public:
    int ibase2;
    Base2() :ibase2(20) {}
    virtual void f() { cout << "Base2::f()" << endl; }
    virtual void g() { cout << "Base2::g()" << endl; }
    virtual void h() { cout << "Base2::h()" << endl; }
};

class Base3 {
public:
    int ibase3;
    Base3() :ibase3(30) {}
    virtual void f() { cout << "Base3::f()" << endl; }
    virtual void g() { cout << "Base3::g()" << endl; }
    virtual void h() { cout << "Base3::h()" << endl; }
};

class Derive : public Base1, public Base2, public Base3 {
public:
    int iderive;
    Derive() :iderive(100) {}
    virtual void f() { cout << "Derive::f()" << endl; }
    virtual void g1() { cout << "Derive::g1()" << endl; }
};

使用图片表示是下面这个样子:

我们可以看到:

  • 每个父类都有自己的虚表。
  • 子类的成员函数被放到了第一个父类的表中。
  • 内存布局中,其父类布局依次按声明顺序排列。
  • 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

砖石型-重复继承

钻石型-重复继承的源代码如下所示。其中,每个类都有两个变量,一个是整形(4字节),一个是字符(1字节),而且还有自己的虚函数,自己overwrite父类的虚函数。如子类D中,f()覆盖了超类的函数, f1() 和f2() 覆盖了其父类的虚函数,Df()为自己的虚函数。

class B
{
public:
    int ib;
    char cb;
public:
    B() :ib(0), cb('B') {}
    virtual void f() { cout << "B::f()" << endl; }
    virtual void Bf() { cout << "B::Bf()" << endl; }
};

class B1 : public B
{
public:
    int ib1;
    char cb1;
public:
    B1() :ib1(11), cb1('1') {}
    virtual void f() { cout << "B1::f()" << endl; }
    virtual void f1() { cout << "B1::f1()" << endl; }
    virtual void Bf1() { cout << "B1::Bf1()" << endl; }
};

class B2 : public B
{
public:
    int ib2;
    char cb2;
public:
    B2() :ib2(12), cb2('2') {}
    virtual void f() { cout << "B2::f()" << endl; }
    virtual void f2() { cout << "B2::f2()" << endl; }
    virtual void Bf2() { cout << "B2::Bf2()" << endl; }
};

class D : public B1, public B2
{
public:
    int id;
    char cd;
public:
    D() :id(100), cd('D') {}
    virtual void f() { cout << "D::f()" << endl; }
    virtual void f1() { cout << "D::f1()" << endl; }
    virtual void f2() { cout << "D::f2()" << endl; }
    virtual void Df() { cout << "D::Df()" << endl; }
};

图解:

钻石型重复继承内存布局:

我们可以看见,最顶端的父类B其成员变量存在于B1和B2中,并被D给继承下去了。而在D中,其有B1和B2的实例,于是B的成员在D的实例中存在两份,一份是B1继承而来的,另一份是B2继承而来的。所以,如果我们使用以下语句,则会产生二义性编译错误:

1
2
3
4
D d;
d.ib = 0;   //二义性错误
d.B1::ib = 1;   //正确
d.B2::ib = 2;   //正确

注意,上面例程中的最后两条语句存取的是两个变量。虽然我们消除了二义性的编译错误,但B类在D中还是有两个实例,这种继承造成了数据的重复,我们叫这种继承为重复继承。重复的基类数据成员可能并不是我们想要的。所以,C++引入了虚基类的概念。

多重继承(有虚函数覆盖)

下面我们再来看看,如果发生虚函数覆盖的情况。

其中:D自身的虚函数与B基类共用了同一个虚函数表,因此也称B为D的主基类(primary base class)。

虚函数替换过程与前面描述类似,只是多了一个虚函数表,多了一次拷贝和替换的过程。

虚函数的调用过程,与前面描述基本类似,区别在于基类指针指向的位置可能不是派生类对象的起始位置,以如下面的程序为例:

参开资料

1. 2.5 虚函数的实现及基本原理

posted @ 2022-06-05 10:28  苏格拉底的落泪  阅读(50)  评论(0编辑  收藏  举报