2014-04-24 5 views
11

हम सी ++ 11-तैयार कंपाइलर्स के लिए हमारे सभी कंपाइलर्स (अगले दो वर्षों के आसपास) माइग्रेट करने जा रहे हैं।क्या यह पैटर्न C++ 03 enum से C++ 11 enum क्लास में स्रोत पिछड़े-संगत माइग्रेशन के लिए ठीक है?

हमारे ग्राहक हमारे शीर्षलेखों का उपयोग करेंगे, और अब हम अपने नए एपीआई के लिए शीर्षकों (स्क्रैच से अधिक या कम) लिखने की स्थिति पर हैं।

तो हमें सी ++ 03 एनम्स (उनके सभी मौसाओं के साथ) रखने के बीच चयन करना होगा, या सी ++ 11 नोटेशन अनुकरण करने के लिए एक रैपिंग क्लास का उपयोग करना होगा क्योंकि अंत में, उन एनम्स को सी + +11।

क्या "LikeEnum" मुहावरे एक व्यवहार्य समाधान के नीचे प्रस्तावित है, या क्या इसके पीछे छिपे हुए अप्रत्याशित आश्चर्य हैं?

template<typename def, typename inner = typename def::type> 
class like_enum : public def 
{ 
    typedef inner type; 
    inner val; 

public: 

    like_enum() {} 
    like_enum(type v) : val(v) {} 
    operator type() const { return val; } 

    friend bool operator == (const like_enum & lhs, const like_enum & rhs) { return lhs.val == rhs.val; } 
    friend bool operator != (const like_enum & lhs, const like_enum & rhs) { return lhs.val != rhs.val; } 
    friend bool operator < (const like_enum & lhs, const like_enum & rhs) { return lhs.val < rhs.val; } 
    friend bool operator <= (const like_enum & lhs, const like_enum & rhs) { return lhs.val <= rhs.val; } 
    friend bool operator > (const like_enum & lhs, const like_enum & rhs) { return lhs.val > rhs.val; } 
    friend bool operator >= (const like_enum & lhs, const like_enum & rhs) { return lhs.val >= rhs.val; } 
}; 

कौन सा हमें उपयोगकर्ता कोड में अवांछनीय परिवर्तन की जरूरत के बिना हमारे enums को उन्नत करने के लिए सक्षम होगा:

// our code (C++03)     |  our code C++11 
// --------------------------------------+--------------------------- 
             | 
struct KlingonType      | enum class Klingon 
{          | { 
    enum type        | Qapla, 
    {          | Ghobe, 
     Qapla,        | Highos 
     Ghobe,        | } ; 
     Highos        | 
    } ;         | 
} ;          | 
             | 
typedef like_enum<KlingonType> Klingon ; | 
             | 
// --------------------------------------+--------------------------- 
//    client code (both C++03 and C++11) 

void foo(Klingon e) 
{ 
    switch(e) 
    { 
     case Klingon::Qapla : /* etc. */ ; break ; 
     default :    /* etc. */ ; break ; 
    } 
} 

नोट: LikeEnum द्वारा Type Safe Enum idiom

नोट 2 प्रेरित था : स्रोत संगतता int में अंतर्निहित रूपांतरण की वजह से संकलन त्रुटि को कवर नहीं करती है: जिन्हें अवांछित समझा जाता है, और ग्राहक को सलाह में अधिसूचित किया जाएगा एन्से-टू-इंटीजर रूपांतरण को स्पष्ट करने के लिए।

+0

दो-से 'आंतरिक' एक 'टेम्पलेट' फ़ंक्शन बनाएं जो SFINAE सुनिश्चित करता है कि अनुरोध किए गए प्रश्न में प्रकार 'आंतरिक' है जो एक-उपयोगकर्ता एक-निहित रूपांतरण से बचने के लिए है? या एक मध्यवर्ती प्रकार का उपयोग करें? सुरक्षित-मूर्ख मुहावरे और मूल रूप से समस्या ... (या वह मुद्दा 'enum's' पर लागू नहीं होगा? – Yakk

उत्तर

5

संक्षिप्त उत्तर हाँ है, यह एक व्यवहार्य समाधान है (एक फिक्स के साथ)।

यहां लंबा जवाब है। :)


आपके पास तुलनात्मक कार्यों के साथ संकलन-समय त्रुटि है, कड़ाई से बोल रही है। यह मानक-अनुरूप कंपेलरों के साथ पोर्टेबिलिटी मुद्दों का कारण बन जाएगा। विशेष रूप से, पर विचार करें:

bool foo(Klingon e) { return e == Klingon::Qapla } 

संकलक operator== की जो अधिभार दोनों (operator type() const के माध्यम से) परोक्ष KlingonType::type को e बदलने और (Klingon(type) के माध्यम से) परोक्ष Klingon को Klingon::Qapla परिवर्तित करने के रूप में उपयोग करने के लिए, एक की जरूरत है पता नहीं करना चाहिए रूपांतरण।

operator type() constexplicit होने की आवश्यकता है इस त्रुटि को ठीक करेगा। बेशक, explicit सी ++ 03 में मौजूद नहीं है। इसका मतलब है कि आपको @Yakk टिप्पणियों में सुझाव देता है और inner के प्रकार के लिए सुरक्षित-बूल मुहावरे के समान कुछ उपयोग करना होगा। operator type() const को हटाकर पूरी तरह से एक विकल्प नहीं है क्योंकि यह अभिन्न प्रकारों को स्पष्ट रूपांतरणों को हटा देगा।

चूंकि आप कहते हैं कि आप अभी भी संभव रूपांतरण के साथ ठीक हैं, एक आसान फिक्स अंतर्निहित enum प्रकार के साथ तुलना कार्यों को परिभाषित करना होगा।तो के अलावा:

friend bool operator == (const like_enum & lhs, const like_enum & rhs) { return lhs.val == rhs.val; } 
friend bool operator != (const like_enum & lhs, const like_enum & rhs) { return lhs.val != rhs.val; } 
friend bool operator < (const like_enum & lhs, const like_enum & rhs) { return lhs.val < rhs.val; } 
friend bool operator <= (const like_enum & lhs, const like_enum & rhs) { return lhs.val <= rhs.val; } 
friend bool operator > (const like_enum & lhs, const like_enum & rhs) { return lhs.val > rhs.val; } 
friend bool operator >= (const like_enum & lhs, const like_enum & rhs) { return lhs.val >= rhs.val; } 

आप भी आवश्यकता होगी:

friend bool operator ==(const like_enum& lhs, const type rhs) { return lhs.val == rhs; } 
friend bool operator !=(const like_enum& lhs, const type rhs) { return lhs.val != rhs; } 
friend bool operator < (const like_enum& lhs, const type rhs) { return lhs.val < rhs; } 
friend bool operator <=(const like_enum& lhs, const type rhs) { return lhs.val <= rhs; } 
friend bool operator > (const like_enum& lhs, const type rhs) { return lhs.val > rhs; } 
friend bool operator >=(const like_enum& lhs, const type rhs) { return lhs.val >= rhs; } 
friend bool operator ==(const type lhs, const like_enum& rhs) { return operator==(rhs, lhs); } 
friend bool operator !=(const type lhs, const like_enum& rhs) { return operator!=(rhs, lhs); } 
friend bool operator < (const type lhs, const like_enum& rhs) { return operator> (rhs, lhs); } 
friend bool operator <=(const type lhs, const like_enum& rhs) { return operator>=(rhs, lhs); } 
friend bool operator > (const type lhs, const like_enum& rhs) { return operator< (rhs, lhs); } 
friend bool operator >=(const type lhs, const like_enum& rhs) { return operator<=(rhs, lhs); } 

ऊपर फिक्सिंग के बाद, वहाँ थोड़ा उल्लेखनीय अंतर शब्दार्थ (संभव किया जा रहा है अंतर्निहित रूपांतरण अनदेखी) है। मुझे मिला एकमात्र अंतर std::is_pod<Klingon>::value का मूल्य <type_traits> से C++ 11 में है। सी ++ 03 संस्करण का उपयोग करके, यह false होगा, जबकि enum class es का उपयोग करते हुए, यह true होगा। अभ्यास में, इसका मतलब है (अनुकूलन के बिना) enum class का उपयोग कर एक रजिस्टर में किया जा सकता है जबकि like_enum संस्करण को स्टैक पर होना आवश्यक है।

क्योंकि आप enum class के अंतर्निहित प्रतिनिधित्व को निर्दिष्ट नहीं करते हैं, sizeof(Klingon) शायद दोनों के लिए समान होगा, लेकिन मैं इस पर भरोसा नहीं करता। विभिन्न कार्यान्वयन द्वारा चुने गए अंतर्निहित प्रतिनिधित्व की अविश्वसनीयता दृढ़ता से टाइप की गई enum एस के पीछे प्रेरणा का हिस्सा थी।

Here's proof क्लैंग ++ 3.0+, जी ++ 4.5+, और एमएसवीसी 11+ के लिए उपरोक्त दो अनुच्छेदों में से।


अब संकलित आउटपुट के संदर्भ में, दोनों स्पष्ट रूप से असंगत एबीआई होंगे। इसका मतलब है कि आपके पूरे कोडबेस को या तो एक या दूसरे का उपयोग करने की आवश्यकता है। वे मिश्रण नहीं करेंगे। मेरे सिस्टम के लिए (ओएसएक्स पर क्लैंग ++ - 3.5), सी ++ 11 संस्करण के लिए उपरोक्त फ़ंक्शन का प्रतीक __Z1f9like_enumI11KlingonTypeNS0_4typeEE है और C++ 11 संस्करण के लिए __Z1f7Klingon है। यह केवल एक मुद्दा होना चाहिए यदि इन्हें लाइब्रेरी फ़ंक्शंस निर्यात किया जाता है।

आउटपुट असेंबली -O2 पर ऑप्टिमाइज़ेशन मोड़ने के बाद क्लैंग ++ और g ++ के लिए मेरे परीक्षण में समान है। संभवतः अन्य अनुकूलन कंपाइलर Klingon से KlingonType::type को अनदेखा करने में सक्षम होंगे। अनुकूलन के बिना, enum class संस्करण अभी भी सभी कन्स्ट्रक्टर और तुलना ऑपरेटर फ़ंक्शन कॉल से बच जाएगा।

+0

एबीआई अंतर कोई मुद्दा नहीं होगा, क्योंकि हम केवल स्रोत पिछड़े संगतता को लक्षित करते हैं: ग्राहक को पुन: संकलित होने की उम्मीद है प्रत्येक बार जब हम हेडर अपडेट करते हैं, तो जिस क्षण हम सी ++ 11 पर जाते हैं, क्लाइंट भी होगा ... अंतर्निहित प्रकार के आकार के लिए, या यहां तक ​​कि is_pod भी, यह कोई मुद्दा नहीं होगा। तो, अंत में, केवल तुलना की थोड़ी सी समस्या बनी हुई है ... और वाह ... अगर मैं आपके उत्तर को दो बार बढ़ा सकता हूं, तो मैं ... :-) – paercebal

संबंधित मुद्दे