2014-05-02 107 views
10

我的一個項目中有很多自定義數據類型,它們都共享一個基類。 我的數據(來自數據庫)有一個數據類型,它通過基類的枚舉進行區分。我的架構允許特定的數據類型專門用於派生類,或者它可以由基類來處理。優化交換機的模板替換

當我構建一個我具體的數據類型,我通常調用構造函數直接:

Special_Type_X a = Special_Type_X("34.34:fdfh-78"); 
a.getFoo(); 

有一些模板魔術這也使得構建這樣的:

Type_Helper<Base_Type::special_type_x>::Type a = Base_Type::construct<Base_Type::special_type_x>("34.34:fdfh-78"); 
a.getFoo(); 

有關的一些值類型枚舉可能沒有專門化所以

Type_Helper<Base_Type::non_specialized_type_1>::Type == Base_Type 

當im從數據庫中獲取數據的數據類型在編譯時間,以便有構造數據類型(從的QVariant)第三條道路稱爲:

Base_Type a = Base_Type::construct(Base_type::whatever,"[email protected]{3,3}"); 

但當然我想正確的構造函數被調用,所以執行該方法的使用看起來像:

switch(t) { 
    case Base_Type::special_type_x: 
     return Base_Type::construct<Base_Type::special_type_x>(var); 
    case Base_Type::non_specialized_type_1: 
     return Base_Type::construct<Base_Type::non_specialized_type_1>(var);    
    case Base_Type::whatever: 
     return Base_Type::construct<Base_Type::whatever>(var);  
    //..... 
} 

此代碼是重複的,因爲基類可以處理新類型(加入到枚舉)爲好,我想出了以下解決方案:

//Helper Template Method 
template <Base_Type::type_enum bt_itr> 
Base_Type construct_switch(const Base_Type::type_enum& bt, const QVariant& v) 
{ 
    if(bt_itr==bt) 
    return Base_Type::construct<bt_itr>(v); 
    return construct_switch<(Base_Type::type_enum)(bt_itr+1)>(bt,v); 
} 

//Specialization for the last available (dummy type): num_types 
template <> 
Base_Type construct_switch<Base_Type::num_types>(const Base_Type::type_enum& bt, const QVariant&) 
{ 
    qWarning() << "Type"<<bt<<"could not be constructed"; 
    return Base_Type(); //creates an invalid Custom Type 
} 

而且我原來的switch語句替換:

return construct_switch<(Base_Type::type_enum)0>(t,var); 

該解決方案按預期工作。然而,編譯後的代碼卻有所不同。儘管最初的開關語句具有O(1)的複雜性,但新的結果導致O(n)複雜性。生成的代碼會遞歸調用我的幫助器方法,直到找到正確的條目。 編譯器爲什麼不能正確地優化它?有沒有更好的方法來解決這個問題?

類似的問題: Replaceing switch statements when interfaceing between templated and non-templated code

我要指出,我想避免C++ 11和C++ 14和堅持C++ 03。

回答

22

這是魔術開關問題 - 如何獲取(範圍)運行時間值並將其轉化爲編譯時間常量。

開始用C++ 1Y置換的樣板:

template<unsigned...> struct indexes {typedef indexes type;}; 
template<unsigned max, unsigned... is> struct make_indexes: make_indexes<max-1, max-1, is...> {}; 
template<unsigned... is> struct make_indexes<0, is...>:indexes<is...> {}; 
template<unsigned max> using make_indexes_t = typename make_indexes<max>::type; 

現在我們可以創建無符號整數的編譯時序列從0到n-1容易。 make_indexes_t<50>擴展爲indexes<0,1,2,3, ... ,48, 49>。 C++ 1y版本在對數遞歸步驟中是這樣做的,上面是線性的(在編譯時 - 在運行時沒有任何操作),但是你有超過100個類型嗎?

接下來,我們構建一個回調數組。正如我討厭ç傳統函數指針的語法,我會在一些毫無意義的樣板扔來隱藏它:

template<typename T> using type = T; // pointless boilerplate 

template<unsigned... Is> 
Base_Type construct_runtime_helper(indexes<Is...>, Base_Type::type_enum e, QVariant const& v) { 
    // array of pointers to functions: (note static, so created once) 
    static type< Base_Type(const QVariant&) >* constructor_array[] = { 
    (&Base_Type::construct<Is>)... 
    }; 
    // find the eth entry, and call it: 
    return constructor_array[ unsigned(e) ](v); 
} 
Base_Type construct_runtime_helper(Base_Type::type_enum e, QVariant const& v) { 
    return construct_runtime_helper(make_indexes_t<Base_Type::num_types>(), e, v); 
} 

和鮑勃是你的叔叔。 O(1)陣列查找(使用O(n)設置,理論上可以在您的可執行文件啓動之前完成)

+2

這是一個很好的解決方案!使用C++ 03時我有什麼選擇? – Dreamcooled

+4

@dreamcooled升級您的編譯器,使用chained-'if'語句,使用工具生成代碼,複製麪食,使用預處理器宏生成代碼。 – Yakk

+2

編譯器已升級。完美的作品。謝謝。 – Dreamcooled

1

所有函數是內聯的嗎?我希望一個合理的編譯器優化if樹到switch,但只有當if是在相同的功能。爲了便於攜帶,您可能不想依賴於此。

通過讓construct_switch用一個lambda函數填充std::vector<std::function<Base_Type(const QVariant&)>>,可以通過間接函數調用獲得O(1),該lambda函數執行構造並派發該函數。