问题:html
在写代码时,碰到了一个问题。有一个类多重继承。该类没法用基类指针去指代它。引发的问题。ios
代码实例以下c++
#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上。编译时碰到的问题。ubuntu
multiple_inheritance.cpp:83:25: error: ‘Top’ is an ambiguous base of ‘Bottom’ Top* t = new Bottom();
由这个问题,我去搜索资料。bash
了解了c++ 在实现继承体系中的一些细节。ide
1.什么是虚函数表。函数
2.多重继承的二义性解决方法。虚继承。spa
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) (运行时绑定即动态绑定)..net
详细内容能够查看下面的查看资料。里面讲的挺详细的。指针
个人理解。
由于在继承的时候,若是没有使用虚函数,即关键字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