2008-10-31 51 views
22

我有一個抽象基類,它充當接口。來自兩個派生類的多繼承

我有兩套派生類,它們實現了抽象類的一半。 (一個「set」定義了與初始化有關的抽象虛擬方法,另一個「set」定義了與實際「work」相關的那些)。然後我有派生類,它們使用多重繼承來構造完全定義的類本身不添加任何東西)。

所以:(壞的僞代碼)

class AbsBase { 
    virtual void init() = 0; 
    virtual void work() = 0; 
} 

class AbsInit : public AbsBase { 
    void init() { do_this(); } 
    // work() still abs 
} 

class AbsWork : public AbsBase { 
    void work() { do_this(); } 
    // init() still abs 
} 

class NotAbsTotal : public AbsInit, public AbsWork { 
    // Nothing, both should be defined 
} 

首先,我能做到這一點?我可以從兩個派生自同一個Base的類繼承嗎? (但願如此)。

這是「真正的問題」,雖然(我撒謊了一點,以簡化示例)。

我真的走了,要做的就是非抽象訪問方法添加到基類:

class AbsBase { 
public: 
    void init() { init_impl(); } 
    void work() { work_impl(); } 

private: 
    virtual void init_impl() = 0; 
    virtual void work_impl() = 0; 
} 

因爲,一個常見的成語就是讓所有的虛擬方法專用。不幸的是,現在AbsInit和AbsWork都繼承了這些方法,所以NotAbsTotal繼承了「每個都有兩個」(我意識到我可能在編譯時會屠殺實際發生的事情)。

無論如何,g ++會在嘗試使用該類時抱怨:「對成員init()的請求不明確」。

我假設,如果我使用我的AbsBase類作爲純接口,可以避免這種情況(假設頂部示例有效)。

所以: - 我的方式與我的實施? - 這是虛擬方法私人化的一個限制嗎? - 我如何重構我的代碼來做我想做的事? (提供一個通用的接口,但允許的方式換出實現爲成員函數「套」)

編輯:

看來我不是第一個: http://en.wikipedia.org/wiki/Diamond_problem

似乎虛擬繼承是這裏的解決方案。我以前聽說過虛擬繼承,但是我沒有把頭繞在它周圍。我仍然樂於接受建議。

回答

33

它看起來像你想做虛擬繼承。這是否證明實際上是一個好主意是另一個問題,但這裏是你怎麼做:


class AbsBase {...}; 
class AbsInit: public virtual AbsBase {...}; 
class AbsWork: public virtual AbsBase {...}; 
class NotAbsTotal: public AbsInit, public AbsWork {...}; 

基本上,默認情況下,非虛多重繼承將包括副本的每個基類在派生類,幷包括他們所有的方法。這就是爲什麼你有兩個AbsBase副本 - 並且你的方法使用的原因是模糊的,因爲兩組方法都被加載,所以C++無法知道要訪問哪個副本!

虛擬繼承將所有對虛擬基類的引用壓縮爲一個數據結構。這應該使來自基類的方法再次明確。但是,請注意:如果兩個中間類中有其他數據,則可能會產生一些額外的運行時開銷,以使代碼能夠找到共享的虛擬基類。

1

它可以做到,儘管它給人最多的不安。

你需要使用「虛擬繼承」,其語法是像

class AbsInit: public virtual AbsBase {...}; 
class AbsWork: public virtual AbsBase {...}; 
class NotAbsTotal: public AbsInit, public AbsWork {...}; 

然後,你必須指定要使用的功能:

NotAbsTotal::work() 
{ 
    AbsInit::work_impl(); 
} 

(更新了正確的語法)

1

您需要聲明繼承爲虛擬的:

struct AbsBase { 
      virtual void init() = 0; 
      virtual void work() = 0; 
}; 

struct AbsInit : virtual public AbsBase { 
      void init() { } 
}; 

struct AbsWork : virtual public AbsBase { 
      void work() { } 
}; 

struct NotAbsTotal : virtual public AbsInit, virtual public AbsWork { 
}; 

void f(NotAbsTotal *p) 
{ 
     p->init(); 
} 

NotAbsTotal x; 
+0

這個結果不錯,即使我非虛公共方法。但是,我將不得不嘗試看看在將基類型指針用於派生類型的對象時會發生什麼情況,以確保虛擬繼承像虛函數重載一樣工作:) – mmocny 2008-10-31 20:21:38

+0

當從虛擬基類轉換爲派生類時,總是需要使用dynamic_cast <>,這又要求在虛擬基礎中定義虛擬方法。虛擬基地的簡單使用應該沒有問題 - 它將以多態方式授予對派生類的訪問權限。 – 2008-10-31 23:29:10

0

你必須開始思考你在這裏試圖建模的東西。

公共繼承應該只能用來模擬「isa」關係,例如,狗是一種動物,方形是一種形狀等。

看看斯科特邁爾的書「有效的C++」一本關於面向對象設計的各個方面應該被解釋爲什麼的優秀論文。

編輯:我忘了說,雖然迄今爲止提供的答案在技術上是正確的,但我不認爲他們中的任何一個都能解決你試圖建模的問題,這就是問題的癥結所在!

HTH

歡呼聲,

羅布