2014-03-03 78 views
1

假設我有一些C++代碼,看起來像這樣:C++靜態使用調度模板

class Base { 
    virtual void dummy() = 0; 
    // this is to generate a vtable, but note there is no virtual f() 
}; 

class A : public Base { 
public: 
    void f() { /* ... */ }; 
    void dummy() {}; 
} 

class B : public Base { 
public: 
    void f() { /* different implementation from A */ }; 
    void dummy() {}; 
} 

template<class T1, class T2, class T3> 
void doStuff(T1 &x, T2 &y, T3 &z) { 
    for (i=1; i<100000; ++i) { 
     x.f(); 
     y.f(); 
     z.f(); 
    } 
} 

這樣做的目的是爲了避免內循環中的虛函數調用f(),爲了讓編譯器的優化。 (這顯然是我的實際代碼的簡化版本,關於我的使用案例的詳細信息,請參閱此more specific question)。

這工作正常,如果doStuff的參數的類型在運行時知道,但如果他們不那麼它失敗:

int main() { 
    Base *x = new A(); 
    Base *y = new B(); 
    Base *z = new A(); 

    doStuff(*x, *y, *z); 
    // oops - this instantiates to doStuff(Base &, Base &, Base &) 
    // and there's no Base::f(). 
} 

來解決這個問題(由this answer的建議),它看來我要建立明確的靜態調度功能:

void doStuff(Base &x, Base &y, Base &z) { 
    A *a_x = dynamic_cast<A*>(&x); 
    B *b_x = dynamic_cast<B*>(&x); 
    A *a_y = dynamic_cast<A*>(&y); 
    B *b_y = dynamic_cast<B*>(&y); 
    A *a_z = dynamic_cast<A*>(&z); 
    B *b_z = dynamic_cast<B*>(&z); 
    if (a_x && a_y && a_z) { 
     doStuff(*a_x, &a_y, &a_z); 
    } else if (a_x && a_y && b_z) { 
     doStuff(*a_x, &a_y, &b_z); 
    } 
    // ... and so on for all eight combinations of A and B. 
} 

但是,這是一些很煩人的重複代碼,如果我有沿doStuff線多種功能,將很快得到不可收拾,特別是如果其中任何公頃有四個或更多的論點。

所以我的問題是,有什麼辦法可以避免這個重複的代碼?看起來,模板的一些更巧妙的使用應該能夠消除它,但我不知道如何去做。

+0

模板可能很難,但Boost的預處理器庫可能會幫助您:http://www.boost.org/doc/libs/1_40_0/libs/preprocessor/doc/ –

+2

dynamic_cast將僅適用於具有vtable的層次結構類 –

+1

@Alex它似乎是好的,如果我添加一個虛擬的虛擬方法的基類 - 我已經編輯帖子。 – Nathaniel

回答

1

問題的標題是:「static dispatch」 - 不幸的是,您的問題需要在運行時找到類型...您想從某些基類指針中識別實際類型。這隻會在運行時才起作用。

使用dynamic_cast是一種破解,dynamic_cast的使用通常意味着糟糕的OO設計。順便說一句,我敢打賭,使用dynamic_castdynamic_cast可能會非常慢!),然後靜態函數調用比簡單地將f()虛擬化並將其放到您的基類並調用它的方式要慢。

當然你的情況有點特別,你希望爲所有8種情況執行其他的事情,但這是一件很髒的工作,你不能用C++中的簡短代碼來優雅地解決。有可能建立一個不容易出錯/易於擴展的解決方案,或者一個性能更好的解決方案,但它們都不會簡短和/或優雅。你目前的doStuff()實現是一個較不容易出錯的「防彈」解決方案,另一個醜陋的解決方案剛剛出現在我的腦海中,即使對於很多派生類和類組合(自己的類型枚舉與自己的gettype +開關)但那真是醜陋。

總結:這個問題在C++中沒有很好的解決方案。你必須重新考慮你的設計,否則你必須忍受醜陋的代碼,當涉及到無法用C++優雅地解決的問題:例如,在序列化的情況下,你經常會發現這樣的醜陋代碼...

+0

這是不好的OO設計!不幸的是,糟糕的設計是SIMD並行性是編譯器優化而不是語言特性的自然結果 - 這就是需要以這種方式構建它的原因。請注意,'f()'連續被調用100000次(可以由編譯器進行矢量化),而'dynamic cast'只會調用一次,所以成本相對較低。但是,是的,我傾向於認爲你可能是對的,沒有優雅的方式來做到這一點。 (但它不能傷害要問。) – Nathaniel

0

這是一個經典的多派遣問題。它在文獻中非常發達。其中一個解決方案是訪問者模式(您需要將它應用兩次,因爲您有3個參數可用於分派)。另一個是map<tuple<type_info, type_info, type_info>, StuffDoer>(後一類應該有一個虛擬的doStuff方法,並對其中的所有參數執行dynamic_cast)。

+0

如果你有時間,你能指出我對第二種解決方案的樣子嗎?自90年代以來我沒有使用STL,所以我很難想象代碼。 – Nathaniel