簡體   English   中英

鑽石繼承

[英]Diamond inheritance

假設類D和E和F都從基類B繼承,並且類C繼承自D和E.

(i)B類有多少副本出現在C類?

(ii)如何使用虛擬繼承改變這種情況? 解釋你的答案。

(iii)對於許多可能在C ++中使用多重繼承的情況,Java如何避免需要多重繼承?

以下是我目前的一些想法,但我絕不是C ++方面的專家!

(i)如果C繼承自作為B的子類的D和E,那么D和E在技術上是否是他們的超類的副本? 然后,如果C繼承自D和E,那意味着C中有2個B副本。

(ii)使用虛擬有點類似於在Java中使用Abstract(我認為)。 現在給出這個,這意味着在C中不會有多個B副本,因為實例化將被級聯到所需的級別。 我不確定如何說出我的解釋但是說B有一個叫做print()的函數打印“我是B”而C覆蓋了這個函數put print“我是C”。 如果您在沒有虛擬的情況下在C上調用print(),則最終打印“我是B”,使用虛擬將意味着它將打印“我是C”。

(iii)我的想法是Java可以使用接口來避免使用多重繼承。 您可以實現多個接口,但只能擴展一個類。 我不確定還有什么要添加,所以任何輸入或相關資源都會有所幫助。

(i)和(iii)是對的。 根據我的經驗,大部分時間在C ++中,當我使用多重繼承時,這是因為基礎是接口(這個概念在C ++中沒有關鍵字支持,但它是一個你可以執行的概念)。

(ii)的第一句是正確的,但是你的第二句話是關於虛函數,它與虛函數繼承完全不同。 虛擬繼承意味着只有一個B副本,而DE都具有與其基礎相同的副本。 在函數方面沒有區別,但區別在於B的成員變量(和基類)。

如果有一個函數打印出B的成員變量foo ; 然后在情況(ii)中,這個函數總是打印相同的值,因為只有一個foo ,但是在情況下(i)從D基類調用該函數可能會打印一個不同的值來從E基類調用它。

“鑽石繼承”一詞用兩個詞作為一個好的助記符來包裝所有這些:)

你似乎已經得到了正確的答案,盡管推理需要有效。 這里的關鍵問題是“如果它繼承兩個相同的基類,如何布置C實例的內存?”

i)在類型C的對象的內存布局中有2個基類B的副本。提供的示例是“鑽石繼承”的情況,因為當您繪制依賴/繼承樹時,您基本上繪制鑽石。 鑽石繼承的“問題”主要是詢問如何將對象放在內存中。 C ++采用兩種方法,一種是快速的,一種是復制數據成員,一種是較慢的一種,即“虛擬繼承”。 采用非虛方法的原因是,如果繼承一個沒有數據成員的類(Java中的接口是什么),那么“復制數據成員”就沒有問題,因為它們不存在(看到我在底部的筆記)。 如果您的計划僅使用單繼承,則建議使用非虛擬繼承。

ii)如果你有一個virtual class C ,那么在C ++語言中你想讓編譯器執行英雄行為以確保內存布局中只存在任何/所有基類的一個副本派生類的; 我相信這也會帶來輕微的性能損失。 如果你現在使用'C'實例中的任何'B'成員,它將始終引用內存中的相同位置。 請注意,虛擬繼承與您的函數是否為虛函數無關。

旁白:這也與抽象類的概念完全無關。 要在C ++中創建類抽象,請設置任何方法聲明= 0,如void foo() = 0; ; 對任何方法(包括析構函數)這樣做都足以使整個類抽象化。

iii)Java徹底禁止它。 在Java中,只有單個繼承以及實現任意數量接口的能力。 雖然接口確實授予您“is-a”關系和具有虛擬功能的能力,但它們隱含地避免了C ++對數據布局和鑽石繼承的問題,因為接口無法添加任何數據成員,事實上:沒有關於如何解決任何數據成員的位置的困惑。

iii的一個重要擴展是認識到,如果你碰巧“兩次實現相同的接口”,虛擬函數調用不會受到影響。 原因是該方法將始終執行相同的操作,即使它在您的虛擬表中有多個副本; 它只對您的類的數據起作用,它本身不包含需要消除歧義的數據。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM