C ++ में बिट के झंडे के लिए स्कूप्ड एनम का उपयोग करना


60

एक enum X : int(C #) या enum class X : int(C ++ 11) एक प्रकार है जिसका कोई छिपा हुआ आंतरिक क्षेत्र है, intजो किसी भी मूल्य को पकड़ सकता है। इसके अलावा, एनफ पर कई पूर्वनिर्धारित स्थिरांक Xपरिभाषित किए गए हैं। एनम को इसके पूर्णांक मूल्य और इसके विपरीत में डालना संभव है। यह C # और C ++ 11 दोनों में सही है।

C # enums में न केवल व्यक्तिगत मूल्यों को रखने के लिए उपयोग किया जाता है, बल्कि माइक्रोसॉफ्ट की सिफारिश के अनुसार, झंडे के बिटवाइज़ संयोजनों को रखने के लिए भी उपयोग किया जाता है । इस तरह की दुश्मनी (आमतौर पर, लेकिन जरूरी नहीं) [Flags]विशेषता के साथ सजाए गए हैं । डेवलपर्स के जीवन को आसान बनाने के लिए, बिटवाइज़ ऑपरेटर (या, और आदि ...) अतिभारित होते हैं ताकि आप आसानी से ऐसा कुछ कर सकें (C #):

void M(NumericType flags);

M(NumericType.Sign | NumericType.ZeroPadding);

मैं एक अनुभवी C # डेवलपर हूं, लेकिन अब केवल कुछ दिनों के लिए C ++ प्रोग्रामिंग कर रहा हूं, और मुझे C ++ सम्मेलनों के साथ नहीं जाना जाता है। मैं ठीक उसी तरह से C ++ 11 Enum का उपयोग करने का इरादा रखता हूं जैसा कि मुझे C # करने के लिए किया गया था। C ++ 11 में scoped enums पर बिटवाइज़ ऑपरेटर्स ओवरलोडेड नहीं हैं, इसलिए मैं उन्हें ओवरलोड करना चाहता था

इसने एक बहस का आग्रह किया, और राय तीन विकल्पों के बीच भिन्न प्रतीत होती है:

  1. एन # प्रकार के एक चर का उपयोग बिट फ़ील्ड को पकड़ने के लिए किया जाता है, C # के समान:

    void M(NumericType flags);
    
    // With operator overloading:
    M(NumericType::Sign | NumericType::ZeroPadding);
    
    // Without operator overloading:
    M(static_cast<NumericType>(static_cast<int>(NumericType::Sign) | static_cast<int>(NumericType::ZeroPadding)));
    

    लेकिन यह C ++ 11 के स्कॉप्ड एनमों के दृढ़ता से टाइप किए गए एनम दर्शन का मुकाबला करेगा।

  2. अगर आप किसी समरूप संयोजन को संग्रहित करना चाहते हैं तो एक सादे पूर्णांक का उपयोग करें:

    void M(int flags);
    
    M(static_cast<int>(NumericType::Sign) | static_cast<int>(NumericType::ZeroPadding));
    

    लेकिन यह सब कुछ को कम कर देगा int, जिससे आपको कोई सुराग नहीं मिलेगा कि आपको किस प्रकार की विधि डालनी है।

  3. एक अलग वर्ग लिखें जो ऑपरेटर्स को अधिभारित करेगा और एक छिपे हुए पूर्णांक फ़ील्ड में बिटवाइज़ फ़्लैग को रखेगा:

    class NumericTypeFlags {
        unsigned flags_;
    public:
        NumericTypeFlags () : flags_(0) {}
        NumericTypeFlags (NumericType t) : flags_(static_cast<unsigned>(t)) {}
        //...define BITWISE test/set operations
    };
    
    void M(NumericTypeFlags flags);
    
    M(NumericType::Sign | NumericType::ZeroPadding);
    

    ( user315052 द्वारा पूर्ण कोड )

    लेकिन तब आपके पास कोई IntelliSense या संभावित मूल्यों पर आपको संकेत देने के लिए जो भी समर्थन है।

मुझे पता है कि यह एक व्यक्तिपरक प्रश्न है , लेकिन: मुझे किस दृष्टिकोण का उपयोग करना चाहिए? क्या दृष्टिकोण, यदि कोई है, तो C ++ में सबसे अधिक व्यापक रूप से मान्यता प्राप्त है? बिट फ़ील्ड के साथ काम करते समय आप किस दृष्टिकोण का उपयोग करते हैं और क्यों ?

बेशक, सभी तीन दृष्टिकोण काम करते हैं, मैं तथ्यात्मक और तकनीकी कारणों की तलाश कर रहा हूं, आम तौर पर स्वीकृत सम्मेलनों, और न केवल व्यक्तिगत पसंद।

उदाहरण के लिए, मेरी C # पृष्ठभूमि के कारण मैं C ++ में दृष्टिकोण 1 के साथ जाता हूं। इसका अतिरिक्त लाभ यह है कि मेरा विकास पर्यावरण मुझे संभावित मूल्यों पर संकेत दे सकता है, और अतिभारित एनम ऑपरेटरों के साथ यह लिखना और समझना आसान है, और काफी साफ है। और विधि हस्ताक्षर स्पष्ट रूप से दिखाता है कि यह किस प्रकार का मूल्य है। लेकिन यहां ज्यादातर लोग मुझसे असहमत हैं, शायद अच्छे कारण के लिए।


2
आईएसओ सी ++ समिति ने विकल्प 1 को पर्याप्त रूप से पाया कि स्पष्ट रूप से बताया गया है कि एनमों की मूल्य सीमा में झंडे के सभी बाइनरी संयोजन शामिल हैं। (यह भविष्यवाणी सी ++ 03) तो कुछ हद तक इस व्यक्तिपरक सवाल का एक उद्देश्य अनुमोदन है।
MSalters

1
(@MSalters की टिप्पणी को स्पष्ट करने के लिए, C ++ enum की सीमा इसके अंतर्निहित प्रकार (यदि एक निश्चित प्रकार) पर आधारित है, या अन्यथा इसके प्रगणकों पर आधारित है। उत्तरार्द्ध मामले में, रेंज सबसे छोटे बिटफील्ड पर आधारित है जो सभी परिभाषित एनमरिटेटर को पकड़ सकती है। ; जैसे, के लिए enum E { A = 1, B = 2, C = 4, };, सीमा है 0..7। (3 बिट्स) इस प्रकार, सी ++ मानक स्पष्ट रूप से गारंटी देता है कि # 1 हमेशा एक व्यवहार्य विकल्प हो जाएगा [विशेष रूप से,। enum classडिफ़ॉल्ट enum class : intजब तक अन्यथा निर्दिष्ट, और इस तरह हमेशा एक निश्चित अंतर्निहित प्रकार है।])
जस्टिन टाइम

जवाबों:


31

सबसे सरल तरीका यह है कि ऑपरेटर खुद को ओवरलोड करता है। मैं प्रति प्रकार बुनियादी अधिभार का विस्तार करने के लिए एक मैक्रो बनाने के बारे में सोच रहा हूं।

#include <type_traits>

enum class SBJFrameDrag
{
    None = 0x00,
    Top = 0x01,
    Left = 0x02,
    Bottom = 0x04,
    Right = 0x08,
};

inline SBJFrameDrag operator | (SBJFrameDrag lhs, SBJFrameDrag rhs)
{
    using T = std::underlying_type_t <SBJFrameDrag>;
    return static_cast<SBJFrameDrag>(static_cast<T>(lhs) | static_cast<T>(rhs));
}

inline SBJFrameDrag& operator |= (SBJFrameDrag& lhs, SBJFrameDrag rhs)
{
    lhs = lhs | rhs;
    return lhs;
}

(ध्यान दें कि type_traitsC ++ 11 हेडर है और std::underlying_type_tC ++ 14 फीचर है।)


6
std :: अंतर्निहित_type_t C ++ 14 है। Std :: अंतर्निहित_type <T> :: C ++ 11 प्रकार का उपयोग कर सकते हैं।
ddevienne

14
आप static_cast<T>इनपुट के लिए क्यों उपयोग कर रहे हैं , लेकिन परिणाम के लिए सी-स्टाइल कास्ट यहाँ?
रुस्लान

2
@ रोलन I यह सवाल दूसरा है
ऑडीफैनेटिक

जब आप पहले से ही जानते हैं कि यह int है तो आप std :: अंतर्निहित_type_t से भी परेशान क्यों हैं?
poizan42

1
यदि SBJFrameDragएक वर्ग में परिभाषित किया गया है और |-ऑपरेटर को बाद में उसी कक्षा की परिभाषाओं में उपयोग किया जाता है, तो आप ऑपरेटर को कैसे परिभाषित करेंगे, जिसका उपयोग कक्षा के भीतर किया जा सकता है?
हैलोगूडीबाई

6

ऐतिहासिक रूप से, मैंने हमेशा पुराने (कमजोर-टाइप किए गए) एन्यूमरेशन का उपयोग बिट कॉन्स्टेंट्स के नाम के लिए किया होगा, और परिणामी ध्वज को संग्रहीत करने के लिए स्टोरेज क्लास का स्पष्ट रूप से उपयोग किया। यहाँ, मुझ पर यह सुनिश्चित करने के लिए होगा कि मेरी गणना भंडारण प्रकार में फिट हो, और क्षेत्र के बीच सहयोग का ध्यान रखें और यह संबंधित स्थिरांक है।

मैं दृढ़ता से टाइप किए गए एनम के विचार को पसंद करता हूं, लेकिन मैं इस विचार के साथ वास्तव में सहज नहीं हूं कि एन्यूमरेटेड प्रकार के चर में ऐसे मान हो सकते हैं जो कि एन्यूमरेशन के स्थिरांक के बीच नहीं हैं।

जैसे, बिटवाइज़ मान लेना या ओवरलोड हो गया है:

enum class E1 { A=1, B=2, C=4 };
void test(E1 e) {
    switch(e) {
    case E1::A: do_a(); break;
    case E1::B: do_b(); break;
    case E1::C: do_c(); break;
    default:
        illegal_value();
    }
}
// ...
test(E1::A); // ok
test(E1::A | E1::B); // nope

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

template <size_t Size> struct IntegralTypeLookup;
template <> struct IntegralTypeLookup<sizeof(int64_t)> { typedef uint64_t Type; };
template <> struct IntegralTypeLookup<sizeof(int32_t)> { typedef uint32_t Type; };
template <> struct IntegralTypeLookup<sizeof(int16_t)> { typedef uint16_t Type; };
template <> struct IntegralTypeLookup<sizeof(int8_t)>  { typedef uint8_t Type; };

template <typename IntegralType> struct Integral {
    typedef typename IntegralTypeLookup<sizeof(IntegralType)>::Type Type;
};

template <typename ENUM> class EnumeratedFlags {
    typedef typename Integral<ENUM>::Type RawType;
    RawType raw;
public:
    EnumeratedFlags() : raw() {}
    EnumeratedFlags(EnumeratedFlags const&) = default;

    void set(ENUM e)   { raw |=  static_cast<RawType>(e); }
    void reset(ENUM e) { raw &= ~static_cast<RawType>(e); };
    bool test(ENUM e) const { return raw & static_cast<RawType>(e); }

    RawType raw_value() const { return raw; }
};
enum class E2: uint8_t { A=1, B=2, C=4 };
typedef EnumeratedFlags<E2> E2Flag;

यह अभी भी आपको IntelliSense या स्वतः पूर्णता नहीं देता है, लेकिन स्टोरेज प्रकार का पता लगाने की तुलना में मैं मूल रूप से अपेक्षा से कम बदसूरत है।


अब, मुझे एक विकल्प मिला: आप कमजोर टाइप टाइप एन्यूमरेशन के लिए भंडारण प्रकार निर्दिष्ट कर सकते हैं। यहां तक ​​कि इसमें C # जैसा ही वाक्य विन्यास है।

enum E4 : int { ... };

क्योंकि यह कमजोर-टाइप किया गया है, और अंतर्निहित रूप से / से int (या जो भी भंडारण प्रकार आप चुनते हैं) में कनवर्ट करता है, यह उन मानों को कम अजीब लगता है जो प्रगणित स्थिरांक से मेल नहीं खाते हैं।

नकारात्मक पक्ष यह है कि यह "संक्रमणकालीन" के रूप में वर्णित है ...

एनबी। यह संस्करण अपने एन्यूमरेटेड स्थिरांक को नेस्टेड और एन्क्लोज़िंग स्कोप दोनों में जोड़ता है, लेकिन आप इसके चारों ओर एक नेमस्पेस के साथ काम कर सकते हैं:

namespace E5 {
    enum Enum : int { A, B, C };
}
E5::Enum x = E5::A; // or E5::Enum::A

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

यह सच है। निर्दिष्ट भंडारण प्रकार के साथ कमजोर-टाइप किया गया संस्करण, अपने स्थिरांक को संलग्नक क्षेत्र और इसके स्वयं के दायरे, iiuc दोनों में जोड़ता है।
बेकार

अनकैप्ड एन्यूमरेटर को केवल आसपास के दायरे में घोषित किया जाता है। एनम-नाम द्वारा इसे योग्य बनाने में सक्षम होना लुकअप नियमों का हिस्सा है, घोषणा नहीं। सी ++ 11 7.2 / 10: प्रत्येक एनुम-नाम और प्रत्येक अनकैप्ड एन्यूमरेटर को उस दायरे में घोषित किया जाता है जिसमें तुरंत एनम-स्पेसियर होता है। प्रत्येक स्कूप्ड एन्यूमरेटर को एन्यूमरेशन के दायरे में घोषित किया जाता है। ये नाम (3.3) और (3.4) में सभी नामों के लिए परिभाषित गुंजाइश नियमों का पालन करते हैं।
लार्स विकलंड

1
C ++ 11 के साथ हमारे पास std :: inher_type है जो अंतर्निहित प्रकार को एक एनम प्रदान करता है। तो हमारे पास 'टेम्पलेट <टाइपनेम का नाम इंटीग्रल टाइप> स्ट्रक्चर इंटीग्रल {टाइपेडिफ टाइपनेम का नाम :: अंतर्निहित_टाइप <इंटीग्रल टाइप> :: प्रकार; }; `C ++ 14 में ये और भी अधिक सरल है। };
ईम्र

4

आप उपयोग करके C ++ 11 में टाइप-सुरक्षित एनम फ्लैग को परिभाषित कर सकते हैं std::enable_if। यह एक अल्पविकसित कार्यान्वयन है जो कुछ चीजों को याद कर सकता है:

template<typename Enum, bool IsEnum = std::is_enum<Enum>::value>
class bitflag;

template<typename Enum>
class bitflag<Enum, true>
{
public:
  constexpr const static int number_of_bits = std::numeric_limits<typename std::underlying_type<Enum>::type>::digits;

  constexpr bitflag() = default;
  constexpr bitflag(Enum value) : bits(1 << static_cast<std::size_t>(value)) {}
  constexpr bitflag(const bitflag& other) : bits(other.bits) {}

  constexpr bitflag operator|(Enum value) const { bitflag result = *this; result.bits |= 1 << static_cast<std::size_t>(value); return result; }
  constexpr bitflag operator&(Enum value) const { bitflag result = *this; result.bits &= 1 << static_cast<std::size_t>(value); return result; }
  constexpr bitflag operator^(Enum value) const { bitflag result = *this; result.bits ^= 1 << static_cast<std::size_t>(value); return result; }
  constexpr bitflag operator~() const { bitflag result = *this; result.bits.flip(); return result; }

  constexpr bitflag& operator|=(Enum value) { bits |= 1 << static_cast<std::size_t>(value); return *this; }
  constexpr bitflag& operator&=(Enum value) { bits &= 1 << static_cast<std::size_t>(value); return *this; }
  constexpr bitflag& operator^=(Enum value) { bits ^= 1 << static_cast<std::size_t>(value); return *this; }

  constexpr bool any() const { return bits.any(); }
  constexpr bool all() const { return bits.all(); }
  constexpr bool none() const { return bits.none(); }
  constexpr operator bool() { return any(); }

  constexpr bool test(Enum value) const { return bits.test(1 << static_cast<std::size_t>(value)); }
  constexpr void set(Enum value) { bits.set(1 << static_cast<std::size_t>(value)); }
  constexpr void unset(Enum value) { bits.reset(1 << static_cast<std::size_t>(value)); }

private:
  std::bitset<number_of_bits> bits;
};

template<typename Enum>
constexpr typename std::enable_if<std::is_enum<Enum>::value, bitflag<Enum>>::type operator|(Enum left, Enum right)
{
  return bitflag<Enum>(left) | right;
}
template<typename Enum>
constexpr typename std::enable_if<std::is_enum<Enum>::value, bitflag<Enum>>::type operator&(Enum left, Enum right)
{
  return bitflag<Enum>(left) & right;
}
template<typename Enum>
constexpr typename std::enable_if_t<std::is_enum<Enum>::value, bitflag<Enum>>::type operator^(Enum left, Enum right)
{
  return bitflag<Enum>(left) ^ right;
}

नोट कर number_of_bitsसकते हैं दुर्भाग्य से संकलक द्वारा नहीं भरा जा सकता है, क्योंकि C ++ में किसी एन्यूमरेशन के संभावित मूल्यों को आत्मसात करने का कोई तरीका नहीं है।

संपादित करें: वास्तव में मैं सही खड़ा हूं, number_of_bitsआपके लिए संकलक भरना संभव है ।

ध्यान दें कि यह एक गैर-निरंतर ईनम मान श्रेणी को (बेतहाशा अक्षम) संभाल सकता है। मान लें कि यह एक अच्छा विचार है कि इस तरह से एक पागल के साथ उपरोक्त का उपयोग करना या पागलपन को बढ़ावा मिलेगा:

enum class wild_range { start = 0, end = 999999999 };

लेकिन सभी चीजों पर विचार यह अंत में एक काफी प्रयोग करने योग्य समाधान है। किसी भी उपयोगकर्ता-साइड बिटफ़ीडलिंग की आवश्यकता नहीं है, यह टाइप-सुरक्षित है और इसकी सीमा के भीतर, जितना संभव हो उतना कुशल (मैं std::bitsetयहां कार्यान्वयन गुणवत्ता पर दृढ़ता से झुकाव कर रहा हूं ;))।


मुझे यकीन है कि मैं ऑपरेटरों के कुछ अधिभार से चूक गया।
रुबनेव

2

मैं नफ़रत मेरे सी ++ 14 में मैस्ट्रोस अगले आदमी जितना है, लेकिन मैं इस जगह का उपयोग करने के लिए ले गया हूं, और काफी लोकप्रिय है:

#define ENUM_FLAG_OPERATOR(T,X) inline T operator X (T lhs, T rhs) { return (T) (static_cast<std::underlying_type_t <T>>(lhs) X static_cast<std::underlying_type_t <T>>(rhs)); } 
#define ENUM_FLAGS(T) \
enum class T; \
inline T operator ~ (T t) { return (T) (~static_cast<std::underlying_type_t <T>>(t)); } \
ENUM_FLAG_OPERATOR(T,|) \
ENUM_FLAG_OPERATOR(T,^) \
ENUM_FLAG_OPERATOR(T,&) \
enum class T

बनाने के रूप में सरल का उपयोग करें

ENUM_FLAGS(Fish)
{
    OneFish,
    TwoFish,
    RedFish,
    BlueFish
};

और, जैसा कि वे कहते हैं, प्रमाण पुडिंग में है:

ENUM_FLAGS(Hands)
{
    NoHands = 0,
    OneHand = 1 << 0,
    TwoHands = 1 << 1,
    LeftHand = 1 << 2,
    RightHand = 1 << 3
};

Hands hands = Hands::OneHand | Hands::TwoHands;
if ( ( (hands & ~Hands::OneHand) ^ (Hands::TwoHands) ) == Hands::NoHands)
{
    std::cout << "Look ma, no hands!" << std::endl;
}

जैसा कि आप फिट देखते हैं, किसी भी व्यक्तिगत ऑपरेटर को अनिर्धारित करने के लिए स्वतंत्र महसूस करें, लेकिन मेरी अत्यधिक पक्षपाती राय में, C / C ++ निम्न-स्तर की अवधारणाओं और धाराओं के साथ हस्तक्षेप करने के लिए है, और आप इन बिटवाइज़ ऑपरेटरों को मेरी ठंड, मृत हाथों से बाहर निकाल सकते हैं और मैं आपको सभी अपवित्र मैक्रोज़ और बिट-फ़्लिपिंग मंत्रों के साथ लड़ूंगा जिन्हें मैं उन्हें बनाए रखने के लिए तैयार कर सकता हूं।


2
यदि आप मैक्रोज़ का इतना विरोध करते हैं, तो एक उचित सी ++ निर्माण का उपयोग क्यों न करें और मैक्रोज़ के बजाय कुछ टेम्पलेट ऑपरेटर लिखें? बेशक, टेम्पलेट दृष्टिकोण क्योंकि आप उपयोग कर सकते हैं बेहतर है std::enable_ifसाथ std::is_enumही प्रगणित प्रकार के साथ काम करने के लिए अपने मुक्त ऑपरेटर भार के प्रतिबंधित करने के लिए। मैंने तुलनात्मक संचालकों (उपयोग करते हुए std::underlying_type) और तार्किक टाइपिंग ऑपरेटर को मजबूत टाइपिंग को खोए बिना अंतर को पाटने के लिए भी जोड़ा है । केवल एक चीज मैं मेल नहीं खा सकता bool करने के लिए अंतर्निहित रूपांतरण है, लेकिन flags != 0और !flagsमेरे लिए पर्याप्त हैं।
बंदर 0506

1

आमतौर पर आप पूर्णांक मानों के एक समूह को परिभाषित करते हैं जो एकल-बिट सेट बाइनरी संख्या के अनुरूप होते हैं, फिर उन्हें एक साथ जोड़ते हैं। सी प्रोग्रामर आमतौर पर ऐसा करते हैं।

तो आपके पास (मान सेट करने के लिए बिटशिफ्ट ऑपरेटर का उपयोग करना, उदाहरण के लिए 1 << 2 बाइनरी 100 के समान है)

#define ENUM_1 1
#define ENUM_2 1 << 1
#define ENUM_3 1 << 2

आदि

C ++ में आपके पास अधिक विकल्प हैं, एक नए प्रकार को परिभाषित करते हैं बल्कि एक int (उपयोग टाइपफ़ीड ) और इसी तरह ऊपर मान सेट करें; या एक बिटफील्ड या बूल के वेक्टर को परिभाषित करते हैं । अंतिम 2 बहुत ही कुशल हैं और झंडे से निपटने के लिए बहुत अधिक समझ में आता है। बिटफील्ड में आपको टाइप चेकिंग (और इसलिए इंटैलिजेंस) देने का फायदा है।

मैं कहूँगा (स्पष्ट रूप से व्यक्तिपरक) कि एक C ++ प्रोग्रामर को आपकी समस्या के लिए एक बिटफील्ड का उपयोग करना चाहिए, लेकिन मैं C प्रोग्राम्स में C प्रोग्राम्स द्वारा उपयोग किए जाने वाले #define दृष्टिकोण को बहुत अधिक देखता हूं।

मुझे लगता है कि बिटफील्ड सी # के एनम के सबसे करीब है, क्यों सी # एक बिटफील्ड प्रकार होने के लिए एक एनम को अधिभारित करने की कोशिश अजीब है - एक एनम को वास्तव में "एकल-चयन" प्रकार होना चाहिए।


11
इस तरह से c ++ में मैक्रोज़ का उपयोग करना बुरा है
Bћови

3
सी ++ 14 आपको द्विआधारी शाब्दिक (जैसे 0b0100) को परिभाषित करने की अनुमति देता है ताकि 1 << nप्रारूप अप्रचलित की तरह हो।
रॉब के

शायद आपका मतलब बिटफील्ड के बजाय बिटसेट था ।
जॉर्ज बेलोन

1

नीचे एनुम-झंडे का एक छोटा उदाहरण, सी # की तरह बहुत सुंदर दिखता है।

दृष्टिकोण के बारे में, मेरी राय में: कम कोड, कम बग, बेहतर कोड।

#indlude "enum_flags.h"

ENUM_FLAGS(foo_t)
enum class foo_t
    {
     none           = 0x00
    ,a              = 0x01
    ,b              = 0x02
    };

ENUM_FLAGS(foo2_t)
enum class foo2_t
    {
     none           = 0x00
    ,d              = 0x01
    ,e              = 0x02
    };  

int _tmain(int argc, _TCHAR* argv[])
    {
    if(flags(foo_t::a & foo_t::b)) {};
    // if(flags(foo2_t::d & foo_t::b)) {};  // Type safety test - won't compile if uncomment
    };

ENUM_FLAGS (T) एक मैक्रो है, जिसे enum_flags.h में परिभाषित किया गया है (कम से कम 100 लाइनें, बिना किसी प्रतिबंध के उपयोग करने के लिए स्वतंत्र)।


1
क्या फ़ाइल enum_flags.h आपके प्रश्न के 1 संशोधन के समान है? यदि हाँ, तो आप इसे संदर्भित करने के लिए संशोधन URL का उपयोग कर सकते हैं: http://programmers.stackexchange.com/revisions/205567/1
gnat

+1 अच्छा, साफ दिखता है। मैं अपने SDK प्रोजेक्ट में इसे आज़माऊँगा।
गार्टर क्लैबोर्न

1
@GaretClaborn यह वही है जिसे मैं साफ कहूंगा
sehe

1
बेशक, ::typeवहाँ याद किया । फिक्स्ड: paste.ubuntu.com/23884820
sehe

@ हेज़ हे, टेम्पलेट कोड सुपाठ्य नहीं है और समझ में नहीं आता है। यह जादू टोना क्या है? अच्छा .... यह स्निपेट
लोल

0

बिल्ली को त्वचा देने का एक और तरीका है:

बिट ऑपरेटरों को ओवरलोड करने के बजाय, कम से कम कुछ लोग सिर्फ 4 लाइनर जोड़ना पसंद कर सकते हैं, जिससे आपको डुप्लिकेट किए गए एनम के प्रतिबंध को कम करने में मदद मिलेगी:

#include <cstdio>
#include <cstdint>
#include <type_traits>

enum class Foo : uint16_t { A = 0, B = 1, C = 2 };

// ut_cast() casts the enum to its underlying type.
template <typename T>
inline auto ut_cast(T x) -> std::enable_if_t<std::is_enum_v<T>,std::underlying_type_t<T>>
{
    return static_cast<std::underlying_type_t<T> >(x);
}

int main(int argc, const char*argv[])
{
   Foo foo{static_cast<Foo>(ut_cast(Foo::B) | ut_cast(Foo::C))};
   Foo x{ Foo::C };
   if(0 != (ut_cast(x) & ut_cast(foo)) )
       puts("works!");
    else 
        puts("DID NOT WORK - ARGHH");
   return 0;
}

दी गई ut_cast()बात , आपको हर बार चीज़ टाइप करनी होती है , लेकिन ऊपर की ओर, यह अधिक पठनीय कोड static_cast<>()उत्पन्न करता है, जैसा कि उपयोग में है, अंतर्निहित प्रकार रूपांतरण या operator uint16_t()चीजों की तुलना में।

और यहाँ ईमानदार हो, Fooऊपर के कोड में टाइप का उपयोग करने से इसके खतरे हैं:

कहीं और कोई चर पर स्विच केस कर सकता है fooऔर उम्मीद नहीं करता है कि यह एक से अधिक मूल्य रखता है ...

इसलिए कोड को लैटर ut_cast()करने से सतर्क पाठकों को मदद मिलती है कि कुछ गड़बड़ चल रहा है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.