簡體   English   中英

多重繼承和多態問題

[英]Multiple inheritance and polymorphism questions

考慮這個C ++代碼:

#include <iostream>
using namespace std;

struct B {
    virtual int f() { return 1; }
    int g() { return 2; }
};
struct D1 : public B { // (*)
    int g() { return 3; }
};
struct D2 : public B { // (*)
    virtual int f() { return 4; }
};
struct M : public D1, public D2 {
    int g() { return 5; }
};

int main() {
    M m;
    D1* d1 = &m;
    cout << d1->f()
         << static_cast<D2&>(m).g()
         << static_cast<B*>(d1)->g()
         << m.g();
}

它打印1225 如果我們做虛擬繼承,即添加virtual之前public在標有(*)線,它打印4225

  1. 你能解釋為什么1改為4嗎?
  2. 你能解釋一下static_cast<D2&>(m)static_cast<B*>(d1)含義嗎?
  3. 你怎么不迷失在這種組合中? 你在畫東西嗎?
  4. 在普通項目中發現這種復雜的設置是否常見?

圖片勝於雄辯,所以在答案之前......


M類層次結構沒有 D1和D2的B的虛擬基礎繼承:

    M
   / \
  D1 D2
  |   |
  B   B

B為D1和D2的虛擬基礎繼承M類層次結構:

    M
   / \
  D1 D2
   \ /
    B

  1. 跨代委托 ,或者我喜歡稱之為兄弟 - 多態性與扭曲。 虛基礎繼承將B :: f()重寫修復為D2:f()。 希望在考慮虛擬函數的實現位置以及它們因繼承鏈而重寫的內容時,圖片可以幫助解釋這一點。

  2. 在這種情況下, static_cast運算符用法驅動從派生到基類類型的轉換。

  3. 閱讀非常糟糕的代碼並了解語言“工作”的基礎知識的經驗很多

  4. 謝天謝地。 這並不常見。 然而,原來的iostream庫會給你做噩夢,如果這總是令人困惑的話。

你能解釋為什么1改為4嗎?

它為什么變為4 因為交叉授權

這是虛擬繼承之前的繼承圖:

B   B
|   |
D1  D2
 \ /
  M

d1D1 ,因此它不知道D2甚至存在,並且它的父( B )不知道D2存在。 唯一可能的結果是調用B::f()

添加虛擬繼承后,基類將合並在一起。

  B
 / \
D1  D2
 \ /
  M

在這里,當你向d1詢問f() ,它會查看其父級。 現在,他們共享相同的B ,所以Bf()將被D2::f()覆蓋,你得到4

是的,這很奇怪,因為這意味着D1已經設法從D2調用一個函數,這對於它一無所知。 這是C ++中比較奇怪的部分之一,通常可以避免。


你能解釋一下static_cast(m)和static_cast(d1)的含義嗎?

你不明白什么? 他們分別將md1投射到D2&B*


你怎么不迷失在這種組合中? 你在畫東西嗎?

不是在這種情況下。 它很復雜,但足夠小,可以保持在你的頭腦中。 我在上面的例子中繪制了圖表,以使事情盡可能清晰。


在普通項目中發現這種復雜的設置是否常見?

不。每個人都知道要避免可怕的鑽石繼承模式,因為它太復雜了,而且通常有一種更簡單的方法可以做任何你想做的事情。

一般來說,最好選擇合成而不是繼承

這個問題實際上是多個問題:

  1. 為什么在使用非virtual繼承時不會覆蓋virtual函數B::f() 答案當然是你有兩個Base對象:一個作為D1的基礎覆蓋f() ,另一個作為D2的基礎,不覆蓋f() 根據您在調用f()時認為您的對象派生的分支,您將得到不同的結果。 當您將設置更改為只有一個B子對象時,將考慮繼承圖中的任何覆蓋(如果兩個分支都覆蓋它,我認為除非您在再次合並分支的位置覆蓋它,否則您將收到錯誤。
  2. static_cast<D2&>(m)是什么意思? 由於有兩個版本的f()來自Base ,你需要選擇你想要的版本。 使用static_cast<D2&>(m)您可以將M視為D2對象。 如果沒有強制轉換,編譯器將無法分辨您正在查看的兩個主題中的哪一個,並且會產生歧義錯誤。
  3. static_cast<B*>(d1)是什么意思? 它恰好是不必要的,但只將對象視為B*對象。

一般來說,我傾向於避免任何不重要的事情的多重繼承。 大多數時候我使用多重繼承來利用空基優化或創建具有可變數量成員的東西(想想std::tuple<...> )。 我不確定我是否曾經遇到過使用多重繼承來處理生產代碼中的多態的實際需要。

(1)你能解釋為什么1改為4嗎?

沒有virtual繼承,有2個獨立的繼承層次結構; B->D1->MB->D2->M 所以想象一下2個virtual函數表(盡管這是實現定義的)。
當你調用f()D1*它只知道B::f() ,就是這樣。 使用virtual繼承,基class B被委托給M ,因此D2::f()被認為是class M一部分。

(2)你能解釋一下static_cast<D2&>(m)static_cast<B*>(d1)含義嗎?

static_cast<D2&>(m) ,就像將class M對象視為class D2 class M一樣
static_cast<B*>(d1) ,就像將class D1 class B1指針視為class B1 class D1一樣。
兩者都是有效的演員表。
由於g()不是virtual因此函數選擇在編譯時發生。 如果它是virtual那么所有這些鑄造都無關緊要。

(3)你是如何在這種組合中迷失方向的? 你在畫東西嗎?

當然,它很復雜,乍一看如果有這么多類,一個人可能很容易迷失。

(4)在正常項目中發現這種復雜的設置是否常見?

完全沒有 ,這是不尋常的,有時是代碼味道。

1)你能解釋為什么1變為4嗎?

如果沒有虛擬繼承, M兩個 B 實例 ,一個用於此“菱形”的每個分支。 其中一個菱形邊緣( D2 )會覆蓋該功能,而另一個( D1 )則不會。 由於d1聲明為D1 ,因此d1->f()表示您希望訪問其功能未被覆蓋的B副本。 如果你要投向D2 ,你會得到不同的結果。

通過使用虛擬繼承,您將B的兩個實例合並為一個,因此一旦MD2::f有效地覆蓋B:f

2)你能解釋一下static_cast<D2&>(m)static_cast<B*>(d1)含義嗎?

他們分別投入D2&B* 由於g不是虛擬的,因此調用B:::g

3)你怎么不迷失在這種組合中? 你在畫東西嗎?

有時候;)

4)在正常項目中發現這種復雜的設置是否常見?

不太常見。 實際上,完全沒有多個簡單的虛擬繼承(Java,C#...)就可以獲得完整的語言。

但是,有時它可以使事情變得更容易,特別是在圖書館開發中。

暫無
暫無

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

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