2014-05-02 23 views
10

के टेम्पलेट प्रतिस्थापन को अनुकूलित करें मेरे पास मेरी परियोजनाओं में से एक में बहुत से कस्टम डेटाटाइप हैं जो सभी एक सामान्य बेस क्लास साझा करते हैं। मेरा डेटा (डेटाबेस से आ रहा है) में डेटाटाइप है जो बेस क्लास के enum द्वारा प्रतिष्ठित है। मेरा आर्किटेक्चर एक विशिष्ट डेटाटाइप को व्युत्पन्न वर्ग के साथ विशिष्ट करने की अनुमति देता है या इसे बेस क्लास द्वारा नियंत्रित किया जा सकता है।स्विच

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

के कुछ मूल्यों के लिए:

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

कुछ टेम्पलेट जादू जो भी इस तरह यह निर्माण की अनुमति देता है है:

जब मैं एक मेरी विशिष्ट डेटाटाइप्स निर्माण मैं सामान्य रूप से सीधे निर्माता फोन प्रकार enum कोई विशेषज्ञता तो

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

लेकिन निश्चित रूप से मैं सही निर्माता के नाम से जाना चाहते हैं, इसलिए

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

इस कोड दोहराव है और के बाद से आधार वर्ग नए प्रकार संभाल कर सकते हैं (enum को जोड़ा गया) के रूप में अच्छी तरह से, मैं निम्नलिखित समाधान के साथ आया था: कि विधि के क्रियान्वयन की तरह लग रहे करने के लिए इस्तेमाल

और अपने मूल स्विच बयान के साथ बदल दिया है:

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

यह समाधान काम करता है के रूप में उम्मीद। संकलित कोड हालांकि अलग है। जबकि मूल स्विच स्टेटमेंट में ओ (1) की एक जटिलता थी जिसमें ओ (एन) जटिलता में नए दृष्टिकोण होते थे। जेनरेट कोड रिकर्सिवली से मेरी सहायक विधि को तब तक कॉल करता है जब तक कि यह सही प्रविष्टि न पाएं। संकलक इसे ठीक से अनुकूलित क्यों नहीं कर सकता? क्या इसे हल करने के कोई बेहतर तरीके हैं?

इसी समस्या: Replaceing switch statements when interfaceing between templated and non-templated code

मैं उल्लेख करना चाहिए कि मैं सी ++ 11 और सी ++ 14 से बचने और सी ++ 03 से चिपके करना चाहते हैं।

उत्तर

22

यह जादू स्विच समस्या है - कैसे रन (रेंज) रन टाइम मानों को लेना और इसे संकलन समय स्थिर में बदलना है।

सी ++ 1 वर्ष-प्रतिस्थापित बॉयलरप्लेट साथ

प्रारंभ:

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>। सी ++ 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); 
} 

और बॉब अपने चाचा है। ओ (1) सर लुकअप (ओ (एन) सेटअप के साथ, जो आपके निष्पादन योग्य लॉन्चिंग से पहले सिद्धांत में किया जा सकता है)

+2

यह एक अच्छा समाधान है! सी ++ 03 का उपयोग करते समय मेरे विकल्प क्या हैं? – Dreamcooled

+4

@dreamcooled आपके कंपाइलर को अपग्रेड करें, जंजीर-'if' कथन का उपयोग करें, कोड जेनरेट करने के लिए टूल का उपयोग करें, पास्ता कॉपी करें, कोड उत्पन्न करने के लिए प्रीप्रोसेसर मैक्रोज़ का उपयोग करें। – Yakk

+2

कंपाइलर अपग्रेड किया गया। सही काम करता है। धन्यवाद। – Dreamcooled

1

क्या सभी कार्य इनलाइन हैं? मैं एक उचित संकलक if पेड़ को switch में अनुकूलित करने की अपेक्षा करता हूं, लेकिन केवल if एस एक ही कार्य में हैं। पोर्टेबिलिटी के लिए, आप इस पर भरोसा नहीं करना चाहेंगे।

आप construct_switch लैम्ब्डा कार्यों कि निर्माण करना और फिर उस बंद प्रेषण के साथ एक std::vector<std::function<Base_Type(const QVariant&)>> पॉप्युलेट होने से (1) एक अप्रत्यक्ष समारोह कॉल के साथ हे मिल सकती है।