C++ 虚函数表 多重继承

本文探讨了多重继承情况下虚函数表的排列规律,通过具体的类继承实例与测试代码,详细解释了不同类中虚函数如何在虚函数表中布局,并展示了当子类重写父类虚函数时的具体变化。

        上次研究的是单继承的情况,这次研究多重继承下的虚函数表的排列情况。

        

         这次A,A1,A2,B这几个类的继承关系如下图:

         

              测试代码如下:

#include<iostream>
using namespace std;

class  A
{
public:
        virtual void fun1()
        {
                cout<<"A::fun1"<<endl;
        }
        virtual void fun2()
        {
                cout<<"A::fun2"<<endl;
        }

        virtual void fun3()
        {
                cout<<"A::fun3"<<endl;
        }


};


class A1:public A
{
public:
        virtual void fun1()
        {
                cout<<"A1::fun1"<<endl;
        }

};


class A2:public A
{
public:
        virtual void fun2()
        {
                cout<<"A2::fun2"<<endl;
        }
        virtual void  fun3()
        {
                cout<<"A2::fun3"<<endl;
        }
};

class B :public A1,public A2
{
public:
        virtual void fun4()
        {
                cout<<"B::fun4"<<endl;
        }
};

typedef void (*Fun)(void);
int main(int argc, char* argv[])
{
        A1   a1;

        cout<<"A1虚函数表的地址为:"<<(int *)(&a1)<<endl;

        cout<<"A1第一个虚函数的地址为:"<<(int *) *(int *)(&a1)<<endl;

        Fun fun1,fun2,fun3,fun4;

        fun1=( Fun) ( *(int *) *(int *)(&a1));
        fun1();


        fun2=(Fun) (* ((int *) *(int *)(&a1)+1));
        fun2();

        fun3=(Fun) (* ((int *) *(int *)(&a1)+2));
        fun3();


        A2  a2;

        cout<<"A2虚函数表的地址为:"<<(int *)(&a2)<<endl;
        cout<<"A2第一个虚函数的地址为:"<<(int *) *(int *)(&a2)<<endl;


        fun1=( Fun) ( *(int *) *(int *)(&a2));
        fun1();


        fun2=(Fun) (* ((int *) *(int *)(&a2)+1));
        fun2();

        fun3=(Fun) (* ((int *) *(int *)(&a2)+2));
        fun3();

        B   b;
        cout<<"B的第一个虚函数表的地址为:"<<(int *)(&b)<<endl;
        cout<<"B的第一个虚函数表的第一个虚函数的地址为:"<<(int *) *(int *)(&b)<<endl;


        fun1=( Fun) ( *(int *) *(int *)(&b));
        fun1();


        fun2=(Fun) (* ((int *) *(int *)(&b)+1));
        fun2();

        fun3=(Fun) (* ((int *) *(int *)(&b)+2));
        fun3();

        fun4=(Fun) (* ((int *) *(int *)(&b)+3));
        fun4();

        cout<<"B的第二个虚函数表的地址为:"<<(int *)(&b)+1<<endl;
        cout<<"B的第二个虚函数表的第一个虚函数的地址为:"<<(int *) *((int *)(&b)+1)<<endl;

        fun1=( Fun) ( *(int *) *((int *)(&b)+1));
        fun1();


        fun2=(Fun) (* ((int *) *((int *)(&b)+1)+1));
        fun2();

        fun3=(Fun) (* ((int *) *((int *)(&b)+1)+2));
        fun3();
        return 0;
}

运行结果:

A1虚函数表的地址为:0xbfb2e9ec
A1第一个虚函数的地址为:0x8048ff0
A1::fun1
A::fun2
A::fun3
A2虚函数表的地址为:0xbfb2e9d8
A2第一个虚函数的地址为:0x8048fd8
A::fun1
A2::fun2
A2::fun3
B的第一个虚函数表的地址为:0xbfb2e9d0
B的第一个虚函数表的第一个虚函数的地址为:0x8048fa8
A1::fun1
A::fun2
A::fun3
B::fun4
B的第二个虚函数表的地址为:0xbfb2e9d4
B的第二个虚函数表的第一个虚函数的地址为:0x8048fc0
A::fun1
A2::fun2
A2::fun3

             具体说下B的情况,b分别从A1,A2继承了两个虚函数表,这两个虚函数表也是在对象的开始按照顺序开始排列,先是A1的虚函数表,然后是A2的虚函数表。


              B的第一个虚函数表:(int *)(&b)

              B的第一个虚函数表的第一个元素fun1=  * ((int *)  * (int *)(&b))

              B的第一个虚函数表的第二个元素fun2=  * ((int *)  * (int *)(&b)+1)

              ......

              B的第二个虚函数表:(int *)(&b)+1

              B的第二个虚函数表的第一个元素 fun1=   * ((int *) *((int *)(&b)+1)+0)

              B的第二个虚函数表的第二个元素 fun2=  * ((int *) *((int *)(&b)+1)+1)

结果画成虚函数表如下:

     A的虚函数表图:

     

     A1的虚函数表图:

     

     A2的虚函数表图:

     

    B的虚函数表图:

   

       

     结论:

     多重继承会有多个虚函数表,几重继承,就会有几个虚函数表。这些表按照派生的顺序依次排列,如果子类改写了父类的虚函数,那么就会用子类自己的虚函数覆盖虚函数表的相应的位置,如果子类有新的虚函数,那么就添加到第一个虚函数表的末尾。


注意:这里由于类A1和A2都只有指向虚函数表的指针,所以对于类B而言,这两个指针连续。如果A1有个成员的为int类型,则B的第二个虚函数表:(int *)(&b)+2,例如:

#include<iostream>
using namespace std;

class Base1
{
  public:
    Base1(int num):num_1(num){}
    virtual void foo1() { cout << "Base1:: foo1" << num_1 << endl;}
    virtual void foo2() { cout << "Base1:: foo2" << num_1 << endl;}
    virtual void foo3() { cout << "Base1:: foo3" << num_1 << endl;}
  private:
    int num_1;
};

class Base2
{
  public:
    Base2(int num):num_2(num){}
    virtual void foo1() { cout << "Base2:: foo1" << num_2 << endl;}
    virtual void foo2() { cout << "Base2:: foo2" << num_2 << endl;}
    virtual void foo3() { cout << "Base2:: foo3" << num_2 << endl;}
  private:
    int num_2;
};

class Base3
{
  public:
    Base3(int num):num_3(num){}
    virtual void foo1() { cout << "Base3:: foo1" << num_3 << endl;}
    virtual void foo2() { cout << "Base3:: foo2" << num_3 << endl;}
    virtual void foo3() { cout << "Base3:: foo3" << num_3 << endl;}
  private:
    int num_3;
};

class Derived1:public Base1
{
  public:
    Derived1(int num):Base1(num){}
    virtual void faa1(){ cout << "Dervied1::faa1" <<endl;}
    virtual void faa2() { cout << "Dervied1::faa2" <<endl;}
};

class Derived2:public Base1
{
  public:
    Derived2(int num):Base1(num){}
    virtual void foo2(){ cout << "Dervied2::foo2" << endl;}
    virtual void fbb2(){ cout << "Dervied2::fbb2" << endl;}
    virtual void fbb3(){ cout << "Dervied2::fbb3" << endl;}
};

class Derived3:public Base1,public Base2,public Base3
{
  public:
    Derived3(int num_1,int num_2,int num_3):
         Base1(num_1),Base2(num_2),Base3(num_3){}
    virtual void fcc1(){ cout << "Dervied3::fcc1" << endl;}
    virtual void fcc2(){ cout << "Dervied3::fcc2" << endl;}
};

class Dervied4:public Base1,public Base2,public Base3
{
  public:
    Dervied4(int num_1,int num_2,int num_3):
         Base1(num_1),Base2(num_2),Base3(num_3){}
    virtual void foo1(){ cout << "Dervied4::foo1" << endl;}
    virtual void fdd(){ cout << "Dervied4::fdd" << endl;}
};

typedef void (*Fun)(void);
int main()
{
  Base1* pBase1 = NULL;
  Base2* pBase2 = NULL;
  Base3* pBase3 = NULL;
  Fun pFun;

cout << "--------------------------------" << endl;
  Derived1 d1(1);
  pBase1 = &d1;
  pBase1->foo1();
cout << "------------Derived1----------------" << endl;
  pFun = (Fun)*((int*)*(int*)(&d1)+0);
  pFun();
  pFun = (Fun)*((int*)*(int*)(&d1)+1);
  pFun();
  pFun = (Fun)*((int*)*(int*)(&d1)+2);
  pFun();
  pFun = (Fun)*((int*)*(int*)(&d1)+3);
  pFun();
  pFun = (Fun)*((int*)*(int*)(&d1)+4);
  pFun();

cout << "--------------------------------" << endl;
  Derived2 d2(2);
  pBase1 = &d2;
  pBase1->foo2();
cout << "------------Derived2----------------" << endl;
  pFun = (Fun)*((int*)*(int*)(&d2)+0);
  pFun();
  pFun = (Fun)*((int*)*(int*)(&d2)+1);
  pFun();
  pFun = (Fun)*((int*)*(int*)(&d2)+2);
  pFun();
  pFun = (Fun)*((int*)*(int*)(&d2)+3);
  pFun();
  pFun = (Fun)*((int*)*(int*)(&d2)+4);
  pFun();

cout << "--------------------------------" << endl;
  Derived3 d3(1,2,3);
  pBase1 = &d3;
  pBase2 = &d3;
  pBase3 = &d3;
  pBase1->foo1();
  pBase2->foo1();
  pBase3->foo1();
cout << "------------Derived3----------------" << endl;
  pFun = (Fun)*((int*)*(int*)(&d3)+0);
  pFun();
  pFun = (Fun)*((int*)*(int*)(&d3)+1);
  pFun();
  pFun = (Fun)*((int*)*(int*)(&d3)+2);
  pFun();
  pFun = (Fun)*((int*)*(int*)(&d3)+3);
  pFun();
  pFun = (Fun)*((int*)*(int*)(&d3)+4);
  pFun();

  pFun = (Fun)*((int*)*((int*)(&d3)+2)+0); // Base1 有一个int 类型的成员变量
  pFun();
  pFun = (Fun)*((int*)*((int*)(&d3)+2)+1);
  pFun();
  pFun = (Fun)*((int*)*((int*)(&d3)+2)+2);
  pFun();

  pFun = (Fun)*((int*)*((int*)(&d3)+4)+0); // Base2 有一个int 类型的成员变量
  pFun();
  pFun = (Fun)*((int*)*((int*)(&d3)+4)+1);
  pFun();
  pFun = (Fun)*((int*)*((int*)(&d3)+4)+2);
  pFun();

cout << "--------------------------------" << endl;
  Dervied4 d4(1,2,3);
  pBase1 = &d4;
  pBase2 = &d4;
  pBase3 = &d4;
  pBase1->foo1();
  pBase2->foo1();
  pBase3->foo1();
cout << "------------Derived4----------------" << endl;
  pFun = (Fun)*((int*)*(int*)(&d4)+0);
  pFun();
  pFun = (Fun)*((int*)*(int*)(&d4)+1);
  pFun();
  pFun = (Fun)*((int*)*(int*)(&d4)+2);
  pFun();
  pFun = (Fun)*((int*)*(int*)(&d4)+3);
  pFun();

  pFun = (Fun)*((int*)*((int*)(&d4)+2)+0); // Base1 有一个int 类型的成员变量
  pFun();
  pFun = (Fun)*((int*)*((int*)(&d4)+2)+1);
  pFun();
  pFun = (Fun)*((int*)*((int*)(&d4)+2)+2);
  pFun();

  pFun = (Fun)*((int*)*((int*)(&d4)+4)+0); // Base1 有一个int 类型的成员变量
  pFun();
  pFun = (Fun)*((int*)*((int*)(&d4)+4)+1);
  pFun();
  pFun = (Fun)*((int*)*((int*)(&d4)+4)+2);
  pFun();

  return 0;
}

运行结果:

--------------------------------
Base1:: foo11
------------Derived1----------------
Base1:: foo110843488
Base1:: foo210843488
Base1:: foo310843488
Dervied1::faa1
Dervied1::faa2
--------------------------------
Dervied2::foo2
------------Derived2----------------
Base1:: foo110843488
Dervied2::foo2
Base1:: foo310843488
Dervied2::fbb2
Dervied2::fbb3
--------------------------------
Base1:: foo11
Base2:: foo12
Base3:: foo13
------------Derived3----------------
Base1:: foo110843488
Base1:: foo210843488
Base1:: foo310843488
Dervied3::fcc1
Dervied3::fcc2
Base2:: foo110843488
Base2:: foo210843488
Base2:: foo310843488
Base3:: foo110843488
Base3:: foo210843488
Base3:: foo310843488
--------------------------------
Dervied4::foo1
Dervied4::foo1
Dervied4::foo1
------------Derived4----------------
Dervied4::foo1
Base1:: foo210843488
Base1:: foo310843488
Dervied4::fdd
Dervied4::foo1
Base2:: foo20
Base2:: foo30
Dervied4::foo1
Base3:: foo20
Base3:: foo30
<think>我们正在讨论C++多重继承下的虚函数行为。用户的问题集中在虚函数在多重继承中的行为和用法上。根据提供的引用,特别是引用[4]和引用[5],我们可以得到一些关键点: 引用[4]提到:在多重继承中,如果两个基类有同名的虚函数,那么直接调用会导致二义性,必须重新定义以消除二义性。同时,为了解决基类子对象重复的问题,引入了虚基类(virtualinheritance)的概念。引用[5]展示了一个例子,其中派生类重写了基类的虚函数(tick()),这是虚函数的基本用法。因此,我们需要解释在多重继承中虚函数的行为,包括可能出现的二义性问题以及如何使用虚继承来解决基类子对象重复的问题。同时,我们还需要讨论虚函数表(vtable)在多重继承中的布局(虽然引用中没有直接提到,但这是理解行为的关键)。步骤: 1.解释多重继承中虚函数的基本行为:当派生类继承自多个基类,而这些基类有虚函数时,派生类会包含多个虚函数表指针(每个基类对应一个)。2.讨论二义性问题:如果多个基类有相同签名的虚函数,且派生类没有重写,那么直接调用该函数会导致二义性,编译器无法确定调用哪个基类的版本。 3.解决方法:在派生类中重写该虚函数,并在重写函数中指定调用哪个基类的版本(使用作用域运算符)。 4.引入虚继承:虚继承解决的是基类子对象在派生类中被多次复制的问题(菱形继承问题)。虚继承与虚函数是两个不同的概念,但在多重继承中可能同时出现。 5.虚函数在虚继承中的行为:虚继承可能会影响虚函数表的布局,使得虚函数表的指针指向更复杂,但通常我们不需要关心底层细节,只要理解调用规则即可。 根据以上分析,我们组织回答。注意:由于引用[1][2][3]的内容与当前问题关联不大,我们主要参考[4]和[5]。</think>### C++虚函数在多重继承中的行为解析 在C++多重继承中,虚函数的行为涉及以下关键机制: #### 1. **虚函数表(vtable)结构** 多重继承时,派生类会包含**多个虚函数表指针**: - 每个基类对应独立的虚函数表 - 派生类新增虚函数添加到**第一个基类**的虚函数表中 ```cpp class Base1 { virtual void f(); }; class Base2 { virtual void g(); }; class Derived : public Base1, public Base2 { virtual void h(); // 添加到Base1的vtable }; ``` #### 2. **二义性问题与解决** 当多个基类存在同名虚函数时: ```cpp class BaseA { virtual void vf(); }; class BaseB { virtual void vf(); }; class MI : public BaseA, public BaseB {}; // 直接调用vf()会产生二义性 ``` **解决方案**: - 必须显式重写虚函数消除歧义 - 使用作用域运算符指定版本: ```cpp class MI : public BaseA, public BaseB { public: void vf() override { BaseA::vf(); // 显式指定调用BaseA版本 // 或 BaseB::vf(); } }; ``` #### 3. **虚继承的影响** 虚基类解决"菱形继承"中的子对象冗余问题: ```cpp class Base { virtual void vf(); }; class Mid1: virtual public Base {}; class Mid2: virtual public Base {}; class Final : public Mid1, public Mid2 {}; ``` 此时: - `Final`类**只包含一个Base子对象** - 虚函数调用路径唯一,避免二义性[^4] #### 4. **构造函数调用顺序** 虚继承改变初始化顺序: 1. 虚基类构造函数(深度优先) 2. 非虚基类构造函数(声明顺序) 3. 成员变量初始化 4. 派生类构造函数 ```mermaid graph TD A[虚基类] --> B[非虚基类] B --> C[成员变量] C --> D[派生类构造] ``` #### 5. **虚函数委托机制** 派生类可通过虚函数实现跨基类委托: ```cpp class SisterA { public: virtual void work() { /* 默认实现 */ } }; class SisterB : public virtual SisterA { public: void work() override { if(condition) SisterA::work(); // 委托给基类 else { /* 新实现 */ } } }; ``` 此机制允许在继承链中灵活组合功能[^3] #### 6. **使用建议** 1. 优先使用单继承+组合替代多重继承 2. 虚基类应设计为**无状态接口** 3. 避免在虚基类中声明非虚数据成员 4. 重写冲突函数时使用`override`关键字[^5] > 多重继承增加了对象模型复杂度,虚函数调用性能可能略低于单继承场景,因需通过多个vtable指针间接寻址[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值