2016-07-09 84 views
2

我正在嘗試完成一些學校作業,並且我剛剛注意到可能存在由於重用基類中的函數而導致的問題乘以繼承的派生類。多重繼承派生類:如何在不重複調用基地的情況下重用派生函數

比方說,我有這些類:

class A 
class B: public A 
class C: public A 

class D: public B, public C 

每個類都有這個方法:

virtual void read(ifstream& in); 

class Bclass C,該read()功能還呼籲class A::read()

void B::read(ifstream& in) 
{ 
    A::read(in); 
    /* read method of B*/ 
} 

void C::read(ifstream& in) 
{ 
    A::read(in); 
    /* read method of C*/ 
} 

現在,pr oblem是,當我想爲一個class D功能read(),我實際調用A::read()兩次:

void D::read(ifstream& in) 
{ 
    B::read(in); 
    C::read(in); 
    /* read method of D*/ 
} 

我知道我可以只在一個類(BC)使用使用A::read()的選項,但假設我需要在兩個課程中使用它。

+0

這可能有助於瞭解每個實現中'read()'實際做了什麼。 –

+0

這不是特定於繼承。如果您有獨立(非成員)函數A_read,B_read和C_read,則會發生同樣的情況。也不是在你的設計中,你可能**希望**兩次調用'A :: read',因爲你繼承了A. –

+0

@ n.m的兩個獨立副本。好點,雖然我認爲這個問題的大多數情況都是在繼承的背景下出現的。我想象的是OP將這些類視爲純粹的接口,沒有數據成員,在這種情況下(我認爲?)是否使用虛擬繼承並不重要。我在回答中提到了這一點,但可能還不夠清楚? –

回答

0

解決此問題的非常規方法是使用布爾值,只需爲每個類(B & & C)指定是否應調用我的超類功能。 解決這個問題的另一種方法就是不要在B或C中調用A的函數。

+0

謝謝,您能否更清楚地解釋「非通用」方式?正如我所提到的,我需要使用read函數,可以選擇在B和C中調用A(用於其他用途),所以基本上我必須在每個類中調用A的func。 –

+0

在D類中,您可以調用A類,並將其結果輸出到B和C,然後使用A的結果調用B && C的虛函數。這樣,​​您只需在B && C中設置一個布爾值來關閉選項要使用A的讀取功能,並且基本上在B中使用A中的讀取功能而不使用兩次。 –

+0

我明白了。聽起來像我不得不使用從B或C調用A的讀取功能的選項。謝謝 –

2

這是爲什麼不鼓勵多重繼承(尤其是來自共同祖先)的一個例子。不是因爲它總是很糟糕 - 儘管它通常是! - 更多的是因爲它通常是困難。如果你能找到替代方案,那通常是可取的。不必要。我相信你會思考這個,並決定它是否是最好的設計。但現在,我們在這裏尋找避免重複A::read()和其他陷阱的方法。

我從着名的Dreaded Diamond of Doom開始比喻 - 沒有任何地方像傳說中那樣令人敬畏,值得銘記。這是說明性的,當通過使用虛擬繼承 - 解決由這種鑽石形狀繼承層次結構創建的'重複的基礎成員'問題時,派生類現在完全負責調用所有虛擬基礎的所有構造函數。構造函數調用不會像平常那樣向上鏈接,並且數據成員初始化很奇怪。去谷歌上查詢!

N.B.如果菱形層次結構的頂級類別具有任何數據成員,則應該使用虛擬繼承,以避免重複/引入歧義。這就是它的目的。但回到主要的話題,我用它作爲函數的類比(它並不嚴格要求它)。

這個想法是從虛擬繼承對最終類的需求中獲得靈感,通過以相同的方式處理派生類的read()行爲來手動調用虛擬基類的構造函數:通過使每個派生類的public-面對read()方法完全負責調用所有的基礎。這也使您不僅可以精確控制哪些基地的方法被調用 - 而且還有它們的順序。

怎麼樣?我們可以將每個派生的read()的實際工作分解爲每個類中受保護的「 ementation」函數,並在每個類中提供公共覆蓋的「包裝函數」。然後包裝函數負責將調用各自的類的impl和所需的任何基地,無論爲了你想要的:

class A { 
protected: 
    void read_impl(ifstream &in) { /* A-specific stuff */ } 
public: 
    virtual void read(ifstream &in) 
    { 
     read_impl(in); 
    } 
}; 

class B: public A { // N.B. virtual public if A has data members! 
protected: 
    void read_impl(ifstream &in) { /* B-specific stuff */ } 
public: 
    virtual void read(ifstream &in) 
    { 
     A::read_impl(in); 
     read_impl(in); // B 
    } 
}; 

class C: public A { 
protected: 
    void read_impl(ifstream &in) { /* C-specific stuff */ } 
public: 
    virtual void read(ifstream &in) 
    { 
     A::read_impl(in); 
     read_impl(in); // CMy 
    } 
}; 

class D: public B, public C { 
protected: 
    void read_impl(ifstream &in) { /* D-specific stuff */ } 
public: 
    virtual void read(ifstream &in) 
    { 
     A::read_impl(in); 
     B::read_impl(in); // avoids calling A again from B 
     C::read_impl(in); // ditto from C 
     read_impl(in); // D 
    } 
}; 

有了這個,你得到了什麼基礎的東西每個final類多內斯完全控制,並何時,沒有不必要的重複呼叫。在DRY方面,impl函數意味着不對中間類重複行爲代碼:每個派生的read()中的寫作都是有用的聲明性信息,說明它們如何協調基礎的行爲。你也可以在之間添加額外的東西,等等。

相關問題