問題: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