【C++ Priemr | 15】虚函数表剖析(二)

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

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

测试代码:

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; }
};

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

我们可以看到:

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

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

 

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

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

测试代码: 

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 f() { cout << "Derive::f" << endl; }  //唯一一个覆盖的子类函数
    virtual void g1() { cout << "Derive::g1" << endl; }
};

下图中,我们在子类中覆盖了父类的f()函数。

下面是对于子类实例中的虚函数表的图:

我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

Derive d;
Base1 *b1 = &d;
Base2 *b2 = &d;
Base3 *b3 = &d;
b1->f(); //Derive::f()
b2->f(); //Derive::f()
b3->f(); //Derive::f()
 
b1->g(); //Base1::g()
b2->g(); //Base2::g()
b3->g(); //Base3::g()

安全性

每次写C++的文章,总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述,相信我们对虚函数表有一个比较细致的了解了。水可载舟,亦可覆舟。下面,让我们来看看我们可以用虚函数表来干点什么坏事吧。

1. 通过父类型的指针访问子类自己的虚函数

我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:

Base1 *b1 = new Derive();
b1->g1();  //编译出错

任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)

 

2. 访问non-public的虚函数

另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。如

#include<iostream>
using namespace std;

class Base {
private:
    virtual void f() { cout << "Base::f" << endl; }
};

class Derive : public Base {};

typedef void(*Fun)(void);

int main() 
{
    Derive d;
    Fun  pFun = (Fun)*((int*)*(int*)(&d) + 0);
    pFun();
}

 输出结果:

 

三、多重继承

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

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

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

#include<iostream>
using namespace std;

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; }
};

int main()
{
    typedef void(*Fun)(void);

    Derive d;

    int** pVtab = (int**)&d;

    cout << "[0] Base1::_vptr->" << endl;
    Fun pFun = (Fun)pVtab[0][0];
    cout << "     [0] ";
    pFun();

    pFun = (Fun)pVtab[0][1];
    cout << "     [1] "; pFun();

    pFun = (Fun)pVtab[0][2];
    cout << "     [2] "; pFun();

    pFun = (Fun)pVtab[0][3];
    cout << "     [3] "; pFun();

    pFun = (Fun)pVtab[0][4];
    cout << "     [4] "; cout << pFun << endl;

    cout << "[1] Base1.ibase1 = " << (int)pVtab[1] << endl;

    int s = sizeof(Base1) / 4;

    cout << "[" << s << "] Base2::_vptr->" << endl;
    pFun = (Fun)pVtab[s][0];
    cout << "     [0] "; pFun();

    pFun = (Fun)pVtab[s][1];
    cout << "     [1] "; pFun();

    pFun = (Fun)pVtab[s][2];
    cout << "     [2] "; pFun();

    pFun = (Fun)pVtab[s][3];
    cout << "     [3] ";
    cout << pFun << endl;

    cout << "[" << s + 1 << "] Base2.ibase2 = " << (int)pVtab[s + 1] << endl;

    s = s + sizeof(Base2) / 4;

    cout << "[" << s << "] Base3::_vptr->" << endl;
    pFun = (Fun)pVtab[s][0];
    cout << "     [0] "; pFun();

    pFun = (Fun)pVtab[s][1];
    cout << "     [1] "; pFun();

    pFun = (Fun)pVtab[s][2];
    cout << "     [2] "; pFun();

    pFun = (Fun)pVtab[s][3];
    cout << "     [3] ";
    cout << pFun << endl;

    s++;
    cout << "[" << s << "] Base3.ibase3 = " << (int)pVtab[s] << endl;
    s++;
    cout << "[" << s << "] Derive.iderive = " << (int)pVtab[s] << endl;
}

输出结果:

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

我们可以看到:

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

 

四、重复继承

面我们再来看看,发生重复继承的情况。所谓重复继承,也就是某个基类被间接地重复继承了多次。

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

测试代码: 

#include<iostream>
using namespace std;
 
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; }
 
};
 
int main()
{
    typedef void(*Fun)(void);
    int** pVtab = NULL;
    Fun pFun = NULL;
 
    D d;
    pVtab = (int**)&d;
    cout << "[0] D::B1::_vptr->" << endl;
    pFun = (Fun)pVtab[0][0];
    cout << "     [0] ";    pFun();
    pFun = (Fun)pVtab[0][1];
    cout << "     [1] ";    pFun();
    pFun = (Fun)pVtab[0][2];
    cout << "     [2] ";    pFun();
    pFun = (Fun)pVtab[0][3];
    cout << "     [3] ";    pFun();
    pFun = (Fun)pVtab[0][4];
    cout << "     [4] ";    pFun();
    pFun = (Fun)pVtab[0][5];
    cout << "     [5] 0x" << pFun << endl;
 
    cout << "[1] B::ib = " << (int)pVtab[1] << endl;
    cout << "[2] B::cb = " << (char)pVtab[2] << endl;
    cout << "[3] B1::ib1 = " << (int)pVtab[3] << endl;
    cout << "[4] B1::cb1 = " << (char)pVtab[4] << endl;
 
    cout << "[5] D::B2::_vptr->" << endl;
    pFun = (Fun)pVtab[5][0];
    cout << "     [0] ";    pFun();
    pFun = (Fun)pVtab[5][1];
    cout << "     [1] ";    pFun();
    pFun = (Fun)pVtab[5][2];
    cout << "     [2] ";    pFun();
    pFun = (Fun)pVtab[5][3];
    cout << "     [3] ";    pFun();
    pFun = (Fun)pVtab[5][4];
    cout << "     [4] 0x" << pFun << endl;
 
    cout << "[6] B::ib = " << (int)pVtab[6] << endl;
    cout << "[7] B::cb = " << (char)pVtab[7] << endl;
    cout << "[8] B2::ib2 = " << (int)pVtab[8] << endl;
    cout << "[9] B2::cb2 = " << (char)pVtab[9] << endl;
 
    cout << "[10] D::id = " << (int)pVtab[10] << endl;
    cout << "[11] D::cd = " << (char)pVtab[11] << 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++引入了虚基类的概念。

 

参考资料:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值