2014-03-26 40 views
4

我有一個節點層次結構,其中可能出現「菱形」。將CRTP與虛擬繼承結合使用

每個節點都必須是可克隆的,但我不想寫克隆的方法每一個節點。所以我使用CRTP。

class Node 
{ 
public: 
    Node(){} 
    Node(Fill*) { } 

    virtual ~Node() {} 
    virtual Node * clone() const = 0; 

    virtual void id() { std::cout << "Node\n"; } 
}; 

//==================================================================== 

template <typename Base, typename Derived> 
class NodeWrap : public Base 
{ 
public: 

    NodeWrap() { } 
    NodeWrap(Fill * arg1) : Base(arg1) { } 

    virtual Node *clone() const 
    { 
     return new Derived(static_cast<Derived const &>(*this)); 
    } 
}; 

工作如下:

class NodeA : public NodeWrap<Node, NodeA> 
{ 
public: 
    typedef NodeWrap<Node, NodeA> BaseClass; 

    NodeA() { } 
    NodeA(Fill * f) : BaseClass(f) { } 

    virtual void id() { std::cout << "NodeA\n"; } 

}; 

第一個問題:

有知道VS BUG當 「協方差與虛擬繼承使用」。 有沒有辦法克服這個bug,而且還有協變類型的方法是clone

我改變返回類型爲Node,而不是Base。我可以忍受,但我想有Base的返回類型

第二個問題:發生 問題,當多重繼承來玩。我創建了新的包裝,它繼承virtually

template <typename Base, typename Derived> 
class NodeWrapVirtual : public virtual Base 
{ 
public: 

    NodeWrapVirtual() { } 
    NodeWrapVirtual(Fill * arg1) : Base(arg1) { } 

    virtual Node *clone() const 
    { 
     return new Derived(static_cast<Derived const &>(*this)); 
    } 
}; 

現在建設金剛石結構:

class NodeB : public NodeWrapVirtual<Node, NodeB> 
{ 
public: 
typedef NodeWrapVirtual<Node, NodeB> BaseClass; 

NodeB() { } 
NodeB(Fill * f) : BaseClass(f) { } 

virtual void id() { std::cout << "NodeB\n"; } 
}; 

//==================================================================== 

class NodeC : public NodeWrapVirtual<Node, NodeC> 
{ 
public: 
    typedef NodeWrapVirtual<Node, NodeC> BaseClass; 

    using BaseClass::clone; 

    NodeC() { } 
    NodeC(Fill * f) : BaseClass(f) { } 

    virtual void id() { std::cout << "NodeC\n"; } 
}; 

和有問題的鑽石節點:

class NodeD : public NodeWrap<NodeB, NodeD>, 
       public NodeWrap<NodeC, NodeD> 
{ 
public: 

    typedef NodeWrap<NodeB, NodeD> BaseClassB; 
    typedef NodeWrap<NodeC, NodeD> BaseClassC; 

    NodeD() { } 
    NodeD(Fill * f) : BaseClassB(f), BaseClassC(f) { } 

    using BaseClassB::clone; // (1) 
    virtual NodeD *clone() const { return new NodeD(*this); }  // (2) 

    virtual void id() { std::cout << "NodeD\n"; } 
}; 

哪裏都是2線我很好奇。 (行(1)和(2))

如果這兩行都被刪除,由於存在不明確的clone方法(來自每個父級),因此存在不經意的編譯錯誤。由於我不使用協變返回類型,因此應該爲每個父項使用clone方法,所以我使用line(1)但它不起作用。仍然含糊不清。

於是我就用線(2)和它的作品。

有沒有一種很好的方式,以避免書寫線(2)?

HERE充滿對ideone工作示例。

回答

3

首先,你應該非常小心使用虛繼承與虛基內部成員(看https://stackoverflow.com/a/1193516/1918154,「有效的C++」,第20項:「在公共接口避免數據成員」和http://www.parashift.com/c++-faq-lite/multiple-inheritance.html#faq-25.8)。你的node得到一個指向未使用的fill的指針,但它看起來像你需要它的地方。

當您將和public移動到您的NodeWrap的基類中時,可以解決您的問題。

template <typename Base> 
class InheritVirtual 
    : public virtual Base 
{}; 

template <typename... Bases> 
class InheritBases 
    : public Bases... 
{ 
    virtual Node* clone() const = 0; 
    virtual void id() const = 0; 
}; 

class NodeB : public NodeWrap<InheritVirtual<Node>, NodeB> 
{ 
    //... 
}; 


class NodeC : public NodeWrap<InheritVirtual<Node>, NodeB> 
{ 
    //... 
}; 

class NodeD : public NodeWrap<InheritBases<NodeB,NodeC>, NodeD> 
{ 
    //... 
}; 

運行Example

因爲所謂的統治規則(Dominance in virtual inheritance),所以需要純粹的虛擬方法InheritBases

要解決的問題是在多個基地的情況下將參數傳遞給正確的構造函數的方法。不像Node(這是一個虛擬的基地)可以讓NodeBNodeC有成員變量和非平凡的構造函數。

3

每個虛擬函數在每個派生類中必須具有唯一的最終覆蓋。這與名稱查找無關(對功能的要求不在於名稱),因此using是無關緊要的。

使用多基歸類節點類模板:

template <class Derived, class Base1, class Base2> 
class node2 : // etc 
// or use a variadic template if you have more than two bases 

至於協變的回報,他們是嚴格不必要的,如果方便的話。您可以隨時將每個虛擬功能分成私有虛擬和公共非虛擬。如果你想返回協變智能指針,這是非常方便的,而常規協變返回機制根本不支持。

+0

有關協變返回的更多細節:任何時候你想調用clone(),* *你已經知道你想要調用它的實例的派生最具體的具體類型,在這種情況下,你不會不需要虛擬方法; *或*你不知道它,在這種情況下,你的代碼無論如何都不能使用任何比'Base *'更具體的返回類型。 –

+0

謝謝你的回答 – relaxxx

+0

@j_random_hacker你是對的 – relaxxx