2010-03-12 62 views
5

爲了解決這個問題,這非常簡化。說我有一個層次:基於模板參數在C++中模擬動態調度

struct Base { 
    virtual int precision() const = 0; 
}; 

template<int Precision> 
struct Derived : public Base { 

    typedef Traits<Precision>::Type Type; 

    Derived(Type data) : value(data) {} 
    virtual int precision() const { return Precision; } 

    Type value; 

}; 

我想用簽名非模板功能:

Base* function(const Base& a, const Base& b); 

凡函數的結果的具體類型是相同類型取其ab具有較大的Precision;像下面的僞代碼:

Base* function(const Base& a, const Base& b) { 

    if (a.precision() > b.precision()) 

     return new A(((A&)a).value + A(b.value).value); 

    else if (a.precision() < b.precision()) 

     return new B(B(((A&)a).value).value + ((B&)b).value); 

    else 

     return new A(((A&)a).value + ((A&)b).value); 

} 

AB是特定類型的分別ab。我想要function獨立於有多少個實例Derived進行操作。我想避免比較大規模的typeid()表,雖然RTTI很好的答案。有任何想法嗎?

+2

我想你應該提到你不知道完整的類的類型。你只知道'Base&'。包括我自己在內的幾個答案確實假設你知道確切類型'Derived '。 – 2010-03-12 22:15:11

+0

注意對一個答案的評論:另一個要求是該函數不能是一個模板;它必須具有給定的Base *(Base&Base)簽名。 – 2010-03-12 22:20:12

+0

將限制更清楚地納入問題中。 – 2010-03-12 22:23:26

回答

3

由於模板在編譯時展開,並且在編譯時函數()不知道派生函數(),所以不能在函數()委託給模板化代碼,而無需在所有可能類型的大量列表之間進行選擇它將被實際調用的類型。您需要編譯每個版本的模板化代碼的模板化代碼實例,這些代碼將是必需的,這可能是無限集合。

遵循該邏輯,知道所有可能需要的模板的唯一地方就是Derived類本身。因此,您Derived類應該包括一個成員:

Derived<Precision> *operation(Base& arg2) { 
    Derived<Precision> *ptr = new Derived<Precision>; 
    // ... 
    return ptr; 
} 

然後,您可以定義function像這樣,間接地做調度:

Base* function(const Base& a, const Base& b) { 
    if (a.precision() > b.precision()) 
    return a.operation(b); 
    else 
    return b.operation(a); 
} 

請注意,這是簡化版本;如果您的操作在其參數中不對稱,則需要定義兩個版本的成員函數 - 一個用this代替第一個參數,另一個代替第二個參數。

此外,這忽略了這樣一個事實,即您需要某種方法才能a.operation在不知道派生類型b的情況下獲得適當形式的b.value。您必須自己解決這個問題 - 請注意,它是(通過與之前相同的邏輯)不可能通過b類型的模板來解決此問題,因爲您在運行時調度。解決方案完全取決於您獲得的類型,以及是否有某種方式可以獲得更高的精度,以便在不知道該對象的確切類型的情況下將對象的值從等於或低於對象的精度中提取出來。這可能是不可能的,在這種情況下,你有很長的類型ID匹配。

不過你不必在switch語句中這樣做。您可以將每個Derived類型的一組成員函數上傳到更高精度的函數中。例如:

template<int i> 
upCast<Derived<i> >() { 
    return /* upcasted value of this */ 
} 

然後,你operator成員函數可以在b.upcast<typeof(this)>操作,不會有明確做鑄造得到它所需要的類型的值。你可能不得不明確地實例化這些函數中的一些來編譯它們;我沒有和RTTI做足夠的工作來確定地說。

問題是,如果你有N個可能的精度,你有N個可能的組合,而且其中每個組合都需要單獨編譯代碼。如果你不能在你的function的定義中使用模板,那麼你必須編譯所有這些可能性的N版本,並且不知何故你必須告訴編譯器生成它們,不知何故你必須選擇正確的一個在運行時調度。使用成員函數的訣竅除去了N的其中一個因素,但另一個仍然存在,並且無法使其成爲完全通用的。

+0

這對我來說已經夠用了。第二個因素是因爲'Base'定義了一個用於在運行時轉換爲任意類型的工具。 – 2010-03-12 22:47:55

+0

哦,好!我只是在編輯,建議這樣的設施對於消除第二個因素是有用的。 – 2010-03-12 22:49:50

1

首先,您想讓您的precision成員的值爲static const int,而不是函數,以便您可以在編譯時對其進行操作。在Derived,這將是:

static const int precision = Precision; 

然後,你需要一些輔助結構,以確定最精確的底座/派生類。首先,你需要一個通用的幫手,幫手結構挑取決於布爾兩種類型之一:

template<typename T1, typename T2, bool use_first> 
struct pickType { 
    typedef T2 type; 
}; 

template<typename T1, typename T2> 
struct pickType<T1, T2, true> { 
    typedef T1 type; 
}; 

然後,pickType<T1, T2, use_first>::type將解析爲T1如果use_firsttrue,否則到T2。所以,我們用它來選擇最精確的類型:

template<typename T1, typename T2> 
struct mostPrecise{ 
    typedef pickType<T1, T2, (T1::precision > T2::precision)>::type type; 
}; 

現在,mostPrecise<T1, T2>::type會給你取了兩種具有較大的precision值。所以,你可以將你的功能定義爲:

template<typename T1, typename T2> 
mostPrecise<T1, T2>::type function(const T1& a, const T2& b) { 
    // ... 
} 

而你就有它。

+0

這對我的另一個項目非常有趣和有用,但我特別需要在運行時作出決定。 – 2010-03-12 22:16:01

+0

謝謝。我會留下這個歷史價值,而不是刪除它,然後,看看我的其他答案,在運行時做這個評論。 – 2010-03-12 22:37:25

4

你要求的是multiple dispatch,又名多方法。這不是C++語言的一個特性。

有特殊情況的解決方法,但您無法避免自己做一些實現。

多次調度的一種常見模式稱爲「redispatch」,又名「遞歸延期調度」。基本上,一個虛擬方法解析一個參數類型,然後調用另一個虛擬方法,直到解決所有參數。外部的功能(如果有的話)只是調用這些虛擬方法中的第一個。

假設有n個派生類,將有一個方法來解決的第一個參數,N,以解決第二,N * N來解決第三等等 - 在最壞的情況,無論如何。這是相當數量的手動工作,並且使用基於typeid的條件塊對初始開發來說可能更容易,但對於使用redispatch進行維護更加穩健。

class Base; 
class Derived1; 
class Derived2; 

class Base 
{ 
    public: 
    virtual void Handle (Base* p2); 

    virtual void Handle (Derived1* p1); 
    virtual void Handle (Derived2* p1); 
}; 

class Derived1 : public Base 
{ 
    public: 
    void Handle (Base* p2); 

    void Handle (Derived1* p1); 
    void Handle (Derived2* p1); 
}; 

void Derived1::Handle (Base* p2) 
{ 
    p2->Handle (this); 
} 

void Derived1::Handle (Derived1* p1) 
{ 
    // p1 is Derived1*, this (p2) is Derived1* 
} 

void Derived1::Handle (Derived2* p1) 
{ 
    // p1 is Derived2*, this (p2) is Derived1* 
} 

// etc 

實現這個使用模板的派生類將是困難的,和模板元編程來處理它可能會無法讀取,難以維護和非常脆弱的。不過,使用非模板方法實現分派,然後使用mixin模板(將其基類作爲模板參數的模板類)用附加特性擴展可能並不那麼糟糕。

visitor design pattern與redispatch IIRC(基本實現使用)密切相關。

另一種方法是使用旨在處理該問題的語言,並且有幾個選項可與C++一起使用。一種是使用treecc--一種特定領域的語言來處理AST節點和多派遣操作,像lex和yacc一樣,生成「源代碼」作爲輸出。

它處理調度決策的所有工作都是基於AST節點ID生成switch語句 - 它可以很容易地作爲動態類型的值類ID IYSWIM。但是,這些是您不必編寫或維護的開關語句,這是一個關鍵區別。我遇到的最大問題是AST節點的摧毀處理器被篡改,這意味着除非您做出特別的努力,否則將不會調用成員數據的析構函數,即它最適合用於字段的POD類型。

另一種選擇是使用支持多方法的語言預處理器。其中有一些,部分原因是Stroustrup確實有相當完善的想法支持多方法。 CMM就是其中之一。另一個是Doublecpp。另一個是Frost Project。我相信CMM與Stroustrup描述的最接近,但我沒有檢查。

然而,最終,多派遣只是一種做出運行時決策的方式,並且有很多方法可以處理相同的決策。專業的DSL帶來了相當多的麻煩,所以你通常只會在需要大量多次調度的情況下才能做到這一點。 Redispatch和訪問者模式都是強大的WRT維護,但是以一些複雜性和混亂爲代價。對於簡單的情況,簡單的條件語句可能是更好的選擇,但是要注意,在編譯時檢測未處理的情況的可能性如果不是不可能的話,則很難。

往往是這樣,至少在C++中沒有一個正確的方法來做到這一點。