首先,何為鑽石繼承,顧名思義,在類的繼承過程中,繼承結構是乙個類似菱形(鑽石)的結構就屬於鑽石繼承,如下:
這是乙個最簡單的鑽石繼承。實際上,在複雜的繼承表中,只要子類按不同的繼承路徑回溯到基類有菱形結構,均屬鑽石繼承。下面先看乙個例子,鑽石繼承在c++程式設計中帶來的問題。
1這樣的執行結果是10?還是20呢?結果是10,為什麼?!明明sets的是20,為什麼get的還是10呢?2 #include3
using
namespace
std;
4classa7
intm_x; 8};
9class b : public
a 12
void
set(int
x) 15
};16
class c : public
a 19
intget(void
) 22
};23
class d : public b,public
c 26
};27
int main(void
) 33
要解釋這個問題那酒必須要先搞清楚,d物件在記憶體中是如何存放的,是怎樣布局的。每乙個子類都會有乙個記憶體檢視,在子類裡都包含了它的基類子物件,下面是建立是d物件時,d物件在記憶體中的存放形式。
包含乙個b類的基類子物件和乙個c型別基類子物件,而b和c裡各自有乙個a型別基類子物件,所以可以看到,在d的記憶體布局中有兩個a型別基類子物件。
set函式是類b的成員函式,在執行set函式時,this指標指向b(其實也是指向a,b從a繼承,a存在b中的首位址),所以set執行後,改變的是b裡的a類基類子物件的資料成員的值。同理,get函式得到的是c裡a類基類子物件的資料成員的值。這樣就可以理解這樣的執行結果了。所謂鑽石繼承問題,就是公共基類物件在我們最終的子類物件中有多個副本,多份拷貝,當我們沿著不同的繼承路徑去訪問公共基類子物件時結果會出現不一致。
而我們應該怎樣解決這樣的問題呢?採用虛繼承。我們所期望的d的儲存形式:
我們需要按如下方式修改**:
class b : virtual這樣就解決了。public a //
虛繼承class c : virtual
public a //
虛繼承d(
int x) : b(x),c(x),a(x) {}
在這個過程中,a物件只在d的初始化表中a(x)進行構造(虛基類最先被構造),而在b和c的初始化表中不再對a進行構造(實際上是都有乙個指標指向了d中的a(x),來對a進行構造)。
鑽石繼承,在訪問公共基類成員函式時,如果不是虛繼承,還會引起二義性的錯誤。**如下:
1編譯器會報錯:對成員'foo()'的請求有歧義,備選為 void a::foo() void a::foo()2 #include3
using
namespace
std;
4classa7
void
foo()
10int
m_x;
11};
12class b : public
a 15
void
set (int
x) 18
};19
class c : public
a 22
intget (void
) 25
};26
class d : public b,public
c 29
};30
int main(void
)
依舊用物件d的記憶體檢視來理解,在構建d物件時,裡面存在兩個a類基類子物件,儘管成員函式不存放在類中而在**段,並且只會有乙份,但是編譯器不知道,他會作為兩個繼承函式來處理,用d.foo()來訪問時,編譯器便不知道訪問的是哪乙個基類子物件裡的foo(),所以備選項都是void a::foo()。
而通過虛繼承可以避免通過最終子類訪問其繼承自公共基類的成員函式時引發的名字衝突問題。
鑽石繼承與虛繼承
首先,何為鑽石繼承,顧名思義,在類的繼承過程中,繼承結構是乙個類似菱形 鑽石 的結構就屬於鑽石繼承,如下 這是乙個最簡單的鑽石繼承。實際上,在複雜的繼承表中,只要子類按不同的繼承路徑回溯到基類有菱形結構,均屬鑽石繼承。下面先看乙個例子,鑽石繼承在c 程式設計中帶來的問題。1 2 include3 u...
C 中的多重繼承 鑽石繼承和虛繼承
多重繼承 鑽石繼承 虛繼承 1 多重繼承 在c 中子類可以有多個父類,按照繼承表的順序繼承父類中的所有成員,並按照繼承表呼叫父類的建構函式。在子類中按照繼承順序排列父類,並且會標記每個父類的位置。當父類的指標或引用指向子類物件時,編譯器會自動計算出父類在子類中的位置。2 鑽石繼承 假如乙個子類繼承了...
C 從入門到放棄之 多重繼承 鑽石繼承 虛繼承
include using namespace std class base1 int m i class base2 typedef int m i class derived public base1,public base2 int main 1 虛繼承作用 通過虛繼承可以讓公共基類子物件在末...