1 多重继承问题三:产生多个虚函数表
继承是父类与子类的叠加,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 呢?如下图所示:
直接强制类型转换,将 BaseA* 类型的指针转换为 BaseB* 类型的指针,指向的仍然是 BaseA 的成员函数。并不能调用 BaseB 的成员函数。
1.1 解决方案:dynamic_cast
想要完成指针类型转换的同时改变指向的函数,需要使用 dynamic_cast,自动帮我们完成转换
dynamic_cast 类型转换
- 用于有继承关系的类指针间的转换
- 用于有交叉关系的类指针间的转换
- 具有类型检查功能
- 需要虚函数的支持
2 正确使用多重继承
单继承某个类+实现(多个)接口
如下图所示:
单继承某个类+实现(多个)接口避免了多重继承出现闭合的情乱,也就避免了数据冗余的问题。同时使用 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、父类提供成员函数用于判断指针是否指向当前对象