问题:
-
问题
在写代码时,碰到了一个问题。有一个类多重继承。该类无法用基类指针去指代它。引起的问题。
代码实例如下
#include <iostream>
using namespace std;
class Top
{
public:
Top(){}
virtual ~Top()
{}
virtual void f()
{
cout<<"Top::f"<<endl;
}
virtual void g()
{
cout<<"Top::g()"<<endl;
}
int nData;
};
class Left: public Top
{
public:
Left(){
}
virtual ~Left()
{
}
virtual void f()
{
cout<<"Left::f"<<endl;
}
virtual void g()
{
cout<<"Left::g()"<<endl;
}
};
//int main()
//{
// //Left* t = new Left();
// Top* t = new Left();
// t->Top::f();
//}
class Right: public Top
{
public:
Right();
~Right();
virtual void f()
{
cout<<"Right::f"<<endl;
}
virtual void g()
{
cout<<"Right::g()"<<endl;
}
};
class Bottom: public Left, public Right
{
public:
Bottom();
~Bottom();
virtual void f()
{
cout<<"Bottom::f"<<endl;
}
virtual void g()
{
cout<<"Bottom::g"<<endl;
}
};
int main(){
Top* t = new Bottom();
return 0;
}
在我的ubuntu上。编译时碰到的问题。
multiple_inheritance.cpp:83:25: error: ‘Top’ is an ambiguous base of ‘Bottom’
Top* t = new Bottom();
由这个问题,我去搜索资料。
-
了解了什么
了解了c++ 在实现继承体系中的一些细节。
1.什么是虚函数表。
2.多重继承的二义性解决方法。虚继承。
虚函数表
A virtual method table (VMT), virtual function table, virtual call table, dispath table (分发表), vtable, or vftable is a mechanism used in a programming language to support dynamic dispatch(动态分发) (or run-time method binding) (运行时绑定即动态绑定).
详细内容可以查看下面的查看资料。里面讲的挺详细的。
我的理解。
因为在继承的时候,如果没有使用虚函数,即关键字virtual修饰函数。则用父类指针调用接口方法,则调用的是父类的方法。这是因为在类的虚函数表中(每个类都会有虚函数表,用于动态绑定),该指针指向的位置函数就是父类的方法。(函数其实就是一个二进制地址)。如果用了virtual修饰函数,则这时候用父类指针指向的接口方法,则是子类的方法。
set print object on//显示对象虚函数表
set print vtbl on//显示虚函数表//作用在我的机器上好像是一样的。
在上面的程序中,主函数是如下所示。
int main(){
Top* pt = new Left();
Left *pl = new Left();
Left l;
Fun pFun = NULL;
pt->f();
pt->Top::f();
pFun();
return 0;
}
(gdb) p *pt
$23 = (Left) {<Top> = {_vptr.Top = 0x400e30 <vtable for Left+16>, nData = 0}, <No data fields>}
(gdb) p *pl
$24 = (Left) {<Top> = {_vptr.Top = 0x400e30 <vtable for Left+16>, nData = 0}, <No data fields>}
(gdb) p l
$25 = (Left) {<Top> = {_vptr.Top = 0x400e30 <vtable for Left+16>, nData = 4196784}, <No data fields>}
打印p和pl和l所指向的内存。记得先打开显示对象的内容。
发现指针先指向的内容是对象的数据。
存放的内容是先存储虚地址表,然后存放数据段。
//顺着去打印gdb指向的虚函数表发现如下。
(gdb) p /a *(void**) 0x400e30@10//@10指向的内容是指打印10个。
$39 = {0x400ca6 <Left::~Left()>, 0x400ce0 <Left::~Left()>, 0x400d06 <Left::f()>, 0x400d30 <Left::g()>, 0x0, 0x0, 0x0, 0x400ec0 <_ZTI3Top>,
0x400bd8 <Top::~Top()>, 0x400c06 <Top::~Top()>}
可以发现,虚指针表指向的内容其实是函数入口。
而所有的入口函数都被换成了left的。我们现在把所有的virtual关键字去掉,看一下内存打印出来的函数入口是什么样子的。(这边有点奇怪,我gdb打印的pt和pl的地址居然会是一样的。)
(gdb) x /32a (void**) 0x400c00
0x400c00 <Top::~Top()+14>: 0xffbae8c78948f845 0x8948f8458b48ffff
0x400c10 <Top::~Top()+30>: 0xc3c9fffffceae8c7 0x10ec8348e5894855
0x400c20 <Top::f()+8>: 0x400d84bef87d8948 0x1de800602100bf00
0x400c30 <Top::f()+24>: 0x400970befffffd 0xfffffd20e8c78948
0x400c40 <Top::f()+40>: 0x8348e5894855c3c9 0x8b48f87d894810ec
0x400c50 <Left::Left()+14>: 0xff54e8c78948f845 0xc748f8458b48ffff
0x400c60 <Left::Left()+30>: 0x90c3c900400db000 0x10ec8348e5894855
0x400c70 <Left::~Left()+8>: 0xf8458b48f87d8948 0x4800400db000c748
0x400c80 <Left::~Left()+24>: 0x39e8c78948f8458b 0xb8ffffff
0x400c90 <Left::~Left()+40>: 0xf8458b480c74c085 0xfffffc60e8c78948
0x400ca0 <Left::~Left()+56>: 0x8348e5894855c3c9 0x8b48f87d894810ec
0x400cb0 <Left::~Left()+14>: 0xffaee8c78948f845 0x8948f8458b48ffff
0x400cc0 <Left::~Left()+30>: 0xc3c9fffffc3ae8c7 0x10ec8348e5894855
0x400cd0 <Left::f()+8>: 0x400d8bbef87d8948 0x6de800602100bf00
0x400ce0 <Left::f()+24>: 0x400970befffffc 0xfffffc70e8c78948
主要是想说明,在没有用虚函数的时候,内存存放的地址内容是先Top的所有方法,接着才是top的方法。
2.接下来讲一下虚继承
接着上面的实例
如果我想用基类作为指针指向其派生类Bottom。则需要在Left和right继承父类的时候实用virtual。
这样子编译的时候就可以通过了。
查看资料:
c++的多重继承要慎用: https://www.cnblogs.com/bourneli/archive/2011/12/28/2305264.html
C++虚函数表解析: http://blog.csdn.net/haoel/article/details/1948051
wiki上面的内容:https://en.wikipedia.org/wiki/Virtual_inheritance
RTTI、虚函数和虚基类的实现方式、开销分析及使用指导http://www.baiy.cn/doc/cpp/inside_rtti.htm