2014-05-02 10 views
10

Mam wiele niestandardowych typów danych w jednym z moich projektów, które mają wspólną klasę podstawową. Moje dane (pochodzące z bazy danych) mają typ danych, który wyróżnia element wyliczeniowy klasy bazowej. Moja architektura umożliwia specyficzny typ danych, który specjalizuje się w klasie pochodnej lub może być obsługiwany przez klasę podstawową.Optymalizacja zamiany szablonu przełącznika

Kiedy skonstruować moje konkretne typy danych i normalnie wywołać konstruktor bezpośrednio:

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

Jest jakiś szablon magia, która umożliwia również konstruowanie to tak:

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

Dla niektórych wartościach wpisz enum, więc nie może być specjalizacji, więc

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

Gdy im Pobieranie danych z bazy danych typ danych nie jest znany w czasie kompilacji więc istnieje trzecia droga do skonstruowania typów danych (z QVariant):

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

Ale oczywiście chcę prawidłowe konstruktor być nazywany, więc wdrożenie tej metody wykorzystywane wyglądać:

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);  
    //..... 
} 

Ten kod jest powtarzalne, a ponieważ klasa bazowa może obsługiwać nowe typy (dodawane do wyliczenia), jak również, wymyśliłem następujące rozwiązanie:

//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 
} 

A moja oryginalna instrukcja switch otrzymuje brzmienie:

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

to rozwiązanie działa zgodnie z oczekiwaniami. Skompilowany kod jest jednak inny. O ile oryginalna instrukcja przełączania miała złożoność O (1), nowa aplikacja skutkuje złożonością O (n). Wygenerowany kod rekursywnie wywołuje moją metodę pomocnika, dopóki nie znajdzie prawidłowego wpisu. Dlaczego kompilator nie może zoptymalizować tego poprawnie? Czy istnieją lepsze sposoby rozwiązania tego problemu?

podobny problem: Replaceing switch statements when interfaceing between templated and non-templated code

należy wspomnieć, że chciałbym uniknąć C++ 11 i C++ 14 i trzymać się C++ 03.

Odpowiedz

22

To jest problem z magicznym przełącznikiem - jak przyjmować (zakres) wartości czasu pracy i przekształcić go w stałą czasu kompilacji.

start z C++ 1r-zastąpiona boilerplate:

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; 

Teraz możemy stworzyć sekwencję kompilacji niepodpisanych liczb całkowitych od 0 do n-1 łatwo. make_indexes_t<50> rozwija się do indexes<0,1,2,3, ... ,48, 49>. Wersja C++ 1y robi to w logarytmicznych krokach rekursji, powyższe robi to liniowo (w czasie kompilacji - nic nie jest wykonywane w czasie wykonywania), ale czy masz więcej niż kilka 100 typów?

Następnie tworzymy zestaw wywołań zwrotnych.Jak ja nienawidzę C składni wskaźnik funkcji starszego typu, rzucę w jakimś bezcelowym boilerplate aby go ukryć:

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); 
} 

i Bob jest twoim wujem. Wyszukiwanie tablicy O (1) (z konfiguracją O (n), która teoretycznie mogłaby być wykonana przed uruchomieniem twojego pliku wykonywalnego)

+2

To dobre rozwiązanie! Jakie są moje opcje podczas używania C++ 03? – Dreamcooled

+4

@dreamcooled zaktualizuj swój kompilator, użyj instrukcji połączonych '' ', użyj narzędzia do wygenerowania kodu, skopiuj makaron, użyj makr preprocesora do wygenerowania kodu. – Yakk

+2

Kompilacja zaktualizowana. Działa idealnie. Dzięki. – Dreamcooled

1

Czy wszystkie funkcje są wbudowane? Spodziewam się, że rozsądny kompilator zoptymalizuje drzewo if do switch, ale tylko wtedy, gdy if s są w tej samej funkcji. Dla przenośności możesz nie chcieć polegać na tym.

Możesz uzyskać O (1) z pośrednim wywołaniem funkcji, wypełniając construct_switch zapełzając std::vector<std::function<Base_Type(const QVariant&)>> z funkcjami lambda, które budują, a następnie wysyłają.