1 多重继承问题三:产生多个虚函数表

【C++深度解析】39、被遗弃的多重继承(下)_C++深度解析
继承是父类与子类的叠加,BaseA 和 BaseB 中都有虚函数,那么二者都会有虚函数表,Dervied 继承了这两个类,类中也就有了两个指针,分别指向两个虚函数表。

编程实验:多重继承问题–产生多个虚函数表

// 38-3.cpp
#include<iostream>
using namespace std;
class BaseA
{
public:
    virtual void funcA()
    {
        cout << "BaseA::funcA()" << endl;
    }
};
class BaseB
{
public:
    virtual void funcB()
    {
        cout << "BaseB::funcB()" << endl;
    }
};
class Derived : public BaseA, public BaseB
{
};
int main()
{
    Derived d;
    BaseA* pa = &d;
    BaseB* pb = &d;
    BaseB* pbb = (BaseB*)pa;					// 强制类型转换 BaseA*-->BaseB*
    BaseB* pbc = dynamic_cast<BaseB*>(pa);		// dynamic_cast类型转换
    cout << "sizeof(b) = " << sizeof(d) << endl;
    pa->funcA();
    pb->funcB();
    pbb->funcB();
    cout << endl;
    cout << "pa = " << pa << endl;
    cout << "pb = " << pb << endl;
    cout << "pbb = " << pbb << endl;
    cout << "pbc = " << pbc << endl;
    return 0;
}
  • BaseA,BaseB 中有虚函数,Derived 继承了这两个类,所有就有两个虚函数表,也就有两个指针分别指向这两个虚函数表。所以 Derived 大小为 16。
  • 子类的引用可以转换为父类的指针,根据多态,pa 可以调用类 BaseA 中的函数 funcA(),pb 可以调用类 BaseB 中的函数 funcB()
  • 第 28 行,将BaseA* 类型的指针转换为 BaseB* 类型的指针,会调用什么函数呢?
  • 使用新式类型转换 dynamic_cast 再尝试,最后我们打印四个指针的值。

编译运行:

$ g++ 38-3.cpp -o 38-3
$ ./38-3
sizeof(b) = 16
BaseA::funcA()
BaseB::funcB()
BaseA::funcA()

pa = 0x7ffc0d30ac40
pb = 0x7ffc0d30ac48
pbb = 0x7ffc0d30ac40
pbc = 0x7ffc0d30ac48

从结果可以看出:

  • 类大小为 16,说明子类有两个虚函数表,两个指针。
  • pbb 是 BaseB* 类型的指针,但是调用的确是类 BaseA 中的函数。从最后打印的指针数值上可以看到 pa,pbb 都是指向类 BaseA,pb 和 pbc 指向类 BaseB。

为什么 BaseB* 类型的指针 pbb 会指向类 BaseA 呢?如下图所示:
【C++深度解析】39、被遗弃的多重继承(下)_C++深度解析_02
直接强制类型转换,将 BaseA* 类型的指针转换为 BaseB* 类型的指针,指向的仍然是 BaseA 的成员函数。并不能调用 BaseB 的成员函数。

1.1 解决方案:dynamic_cast

想要完成指针类型转换的同时改变指向的函数,需要使用 dynamic_cast,自动帮我们完成转换

dynamic_cast 类型转换

  • 用于有继承关系的类指针间的转换
  • 用于有交叉关系的类指针间的转换
  • 具有类型检查功能
  • 需要虚函数的支持

2 正确使用多重继承

单继承某个类+实现(多个)接口

如下图所示:
【C++深度解析】39、被遗弃的多重继承(下)_c  学习_03
单继承某个类+实现(多个)接口避免了多重继承出现闭合的情乱,也就避免了数据冗余的问题。同时使用 dynamic_cast 可以避免指针转换带来的问题,保证一个父类指针转换为另一个父类指针时,调用的函数也相应的改变。

// 38-4.cpp
#include<iostream>
using namespace std;
class Base
{
protected:
    int mi;
public:
    Base(int i) { mi = i; }
    int getI()  { return mi; }
    bool equal(Base* obj)
    {
        return this == obj;
    }
};

class Interface1
{
public:
    virtual void add(int i) = 0;
    virtual void minus(int i) = 0;
};
class Interface2
{
public:
    virtual void multiply(int i) = 0;
    virtual void divide(int i) = 0;
};
class Derived : public Base, public Interface1, public Interface2
{
public:
    Derived(int i) : Base(i) {}
    void add(int i)
    {
        mi += i;
    }
    void minus(int i)
    {
        mi -= i;
    }
    void multiply(int i)
    {
        mi *= i;
    }
    void divide(int i)
    {
        if (i != 0)
        {
            mi /= i;
        }
    }
};
int main()
{
    Derived d(100);
    Derived* p = &d;
    Interface1* pInt1 = &d;
    Interface2* pInt2 = &d;
    cout << "p->getI() = " << p->getI() << endl;
    pInt1->add(10);
    pInt2->divide(11);
    pInt1->minus(5);
    pInt2->multiply(8);
    cout << "p->getI() = " << p->getI() << endl;
    cout << endl;
    cout << "pInt1 == p : " << p->equal(dynamic_cast<Base*>(pInt1)) << endl;
    cout << "pInt1 == p : " << p->equal(dynamic_cast<Base*>(pInt2)) << endl;
    return 0;
}
  • Derived 有三个父类,其中一个是类Base,另外两个是接口,避免了数据冗余的问题。
  • 在子类中要实现 equal() 判断指针是否指向当前对象。
  • 将一个父类指针转换为另一个父类指针,使用 dynamic_cast
$ g++  38-4.cpp -o 38-4
$ ./38-4
p->getI() = 100
p->getI() = 40

pInt1 == p : 1
pInt1 == p : 1

工程建议:

  • 先继承一个类,然后实现多个接口
  • 父类中提供 equal() 成员函数,判断指针是否指向当前对象
  • 多重继承相关强制类型转换用 dynamic_cast 完成

3 小结

1、多继承可能出现多个虚函数表指针
2、与多重继承相关强制类型转换用 dynamic_cast 完成
3、工程中采用“单继承多接口”的方式使用多继承
4、父类提供成员函数用于判断指针是否指向当前对象