2010-11-19 7 views
22

कुछ पुराने सी ++ कोड को संशोधित करते समय, मैंने कई bitflags को enums के रूप में परिभाषित किया।सी ++ में सुरक्षित (आर) बिटफ्लैग टाइप करें?

enum FooFlags 
{ 
    FooFlag1 = 1 << 0, 
    FooFlag2 = 1 << 1, 
    FooFlag3 = 1 << 2 
    // etc... 
}; 

यह असामान्य नहीं है, लेकिन यह मुझे परेशान करता है कि जैसे ही आप झंडे गठबंधन करने के लिए शुरू के रूप में, आप प्रकार की जानकारी खो देते हैं।

int flags = FooFlag1 | FooFlag2; // We've lost the information that this is a set of flags relating to *Foo* 

कुछ इतना पता चला है कि मैं onlyone इस से परेशान नहीं कर रहा हूँ पर खोज कर।

एक विकल्प झंडे को # डिफाइन या कॉन्स्ट इंटीग्रल के रूप में घोषित करना है, इसलिए बिटवाई ऑपरेशंस प्रकार (संभवतः) को परिवर्तित नहीं करेगा। इसके साथ समस्या यह है कि यह हमारे बिट सेट को इंक या अन्य enums के माध्यम से, असंबंधित झंडे के साथ मिलकर करने की अनुमति देता है।

मैं std::bitset और boost::dynamic_bitset से परिचित हूं, लेकिन न ही मेरे मुद्दे को हल करने के लिए डिज़ाइन किया गया है। मैं जो खोज रहा हूं वह सी # FlagsAttribute जैसा कुछ है।

मेरा सवाल यह है कि बिटफ्लैग के एक (अधिक) प्रकार के सुरक्षित सेट के लिए अन्य समाधान क्या हैं?

मैं नीचे अपना स्वयं का समाधान पोस्ट करूंगा।

उत्तर

12

यहाँ अपने ही समाधान है, ग के तत्वों ++ 0x कि VS2010 के वर्तमान संस्करण के लिए अनुमति देता है का उपयोग करते हुए:

#include <iostream> 
#include <numeric> 
#include <string> 

#include <initializer_list> 

template <typename enumT> 
class FlagSet 
{ 
    public: 

     typedef enumT      enum_type; 
     typedef decltype(enumT()|enumT()) store_type; 

     // Default constructor (all 0s) 
     FlagSet() : FlagSet(store_type(0)) 
     { 

     } 

     // Initializer list constructor 
     FlagSet(const std::initializer_list<enum_type>& initList) 
     { 
      // This line didn't work in the initializer list like I thought it would. It seems to dislike the use of the lambda. Forbidden, or a compiler bug? 
      flags_ = std::accumulate(initList.begin(), initList.end(), store_type(0), [](enum_type x, enum_type y) { return x | y; }) 
     } 

     // Value constructor 
     explicit FlagSet(store_type value) : flags_(value) 
     { 

     } 

     // Explicit conversion operator 
     operator store_type() const 
     { 
      return flags_; 
     } 

     operator std::string() const 
     { 
      return to_string(); 
     } 

     bool operator [] (enum_type flag) const 
     { 
      return test(flag); 
     } 

     std::string to_string() const 
     { 
      std::string str(size(), '0'); 

      for(size_t x = 0; x < size(); ++x) 
      { 
       str[size()-x-1] = (flags_ & (1<<x) ? '1' : '0'); 
      } 

      return str; 
     } 

     FlagSet& set() 
     { 
      flags_ = ~store_type(0); 
      return *this; 
     } 

     FlagSet& set(enum_type flag, bool val = true) 
     { 
      flags_ = (val ? (flags_|flag) : (flags_&~flag)); 
      return *this; 
     } 

     FlagSet& reset() 
     { 
      flags_ = store_type(0); 
      return *this; 
     } 

     FlagSet& reset(enum_type flag) 
     { 
      flags_ &= ~flag; 
      return *this; 
     } 

     FlagSet& flip() 
     { 
      flags_ = ~flags_; 
      return *this; 
     } 

     FlagSet& flip(enum_type flag) 
     { 
      flags_ ^= flag; 
      return *this; 
     } 

     size_t count() const 
     { 
      // http://www-graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan 

      store_type bits = flags_; 
      size_t total = 0; 
      for (; bits != 0; ++total) 
      { 
       bits &= bits - 1; // clear the least significant bit set 
      } 
      return total; 
     } 

     /*constexpr*/ size_t size() const // constexpr not supported in vs2010 yet 
     { 
      return sizeof(enum_type)*8; 
     } 

     bool test(enum_type flag) const 
     { 
      return (flags_ & flag) > 0; 
     } 

     bool any() const 
     { 
      return flags_ > 0; 
     } 

     bool none() const 
     { 
      return flags == 0; 
     } 

    private: 

     store_type flags_; 

}; 

template<typename enumT> 
FlagSet<enumT> operator & (const FlagSet<enumT>& lhs, const FlagSet<enumT>& rhs) 
{ 
    return FlagSet<enumT>(FlagSet<enumT>::store_type(lhs) & FlagSet<enumT>::store_type(rhs)); 
} 

template<typename enumT> 
FlagSet<enumT> operator | (const FlagSet<enumT>& lhs, const FlagSet<enumT>& rhs) 
{ 
    return FlagSet<enumT>(FlagSet<enumT>::store_type(lhs) | FlagSet<enumT>::store_type(rhs)); 
} 

template<typename enumT> 
FlagSet<enumT> operator^(const FlagSet<enumT>& lhs, const FlagSet<enumT>& rhs) 
{ 
    return FlagSet<enumT>(FlagSet<enumT>::store_type(lhs)^FlagSet<enumT>::store_type(rhs)); 
} 

template <class charT, class traits, typename enumT> 
std::basic_ostream<charT, traits> & operator << (std::basic_ostream<charT, traits>& os, const FlagSet<enumT>& flagSet) 
{ 
    return os << flagSet.to_string(); 
} 

इंटरफ़ेस std::bitset के बाद मॉडलिंग की है। मेरा उद्देश्य टाइप सुरक्षा और न्यूनतम (यदि कोई है) ओवरहेड के सी ++ आचारों के लिए सच होना था। मैं अपने कार्यान्वयन पर किसी भी प्रतिक्रिया का स्वागत करता हूं।

#include <iostream> 

enum KeyMod 
{ 
    Alt  = 1 << 0, // 1 
    Shift = 1 << 1, // 2 
    Control = 1 << 2 // 4 
}; 

void printState(const FlagSet<KeyMod>& keyMods) 
{ 
    std::cout << "Alt is "  << (keyMods.test(Alt)  ? "set" : "unset") << ".\n"; 
    std::cout << "Shift is " << (keyMods.test(Shift) ? "set" : "unset") << ".\n"; 
    std::cout << "Control is " << (keyMods.test(Control) ? "set" : "unset") << ".\n"; 
} 

int main(int argc, char* argv[]) 
{ 
    FlagSet<KeyMod> keyMods(Shift | Control); 

    printState(keyMods); 

    keyMods.set(Alt); 
    //keyMods.set(24); // error - an int is not a KeyMod value 
    keyMods.set(Shift); 
    keyMods.flip(Control); 

    printState(keyMods); 

    return 0; 
} 
+0

आपके कार्यान्वयन के लिए उपयोग उदाहरण का कोई मौका? – Eric

+0

@Eric, मुझे लगता है कि यह बहुत सीधे आगे होगा। आप वास्तव में क्या खोज रहे हैं? – luke

+0

एक एनम 'ई' घोषित करने का एक सरल उदाहरण, 'फ्लैगसेट ' को तुरंत चालू करना, और इसका उपयोग करना। निश्चित रूप से, मैं इसे काम कर सकता हूं, लेकिन एक उदाहरण इस जवाब को बेहतर बना देगा। – Eric

22

आप गणन प्रकार है कि उचित टाइप परिणाम लौटने के लिए ऑपरेटरों को ओवरलोड कर सकते हैं:

यहाँ एक न्यूनतम उदाहरण है।

inline FooFlags operator|(FooFlags a, FooFlags b) { 
    return static_cast<FooFlags>(+a | +b); 
} 

ऐसा नहीं है कि सैद्धांतिक रूप से सुरक्षित रखने के लिए ध्यान दिया जाना चाहिए, तो आप मैन्युअल उच्चतम संभव मूल्य इतना गणना प्रकार की रेंज सभी संयोजनों को पकड़ने के लिए गारंटी है की घोषणा करनी चाहिए।

  • वास्तव में जरूरत है कि नहीं: एक गणना की सीमा हमेशा सभी संयोजन को पकड़ने के लिए सक्षम हो जाएगा, हमेशा होता है क्योंकि एक गणन की सीमा के उच्चतम सकारात्मक मूल्य पहले N उच्चतम प्रगणक का प्रतिनिधित्व करने में सक्षम होने के लिए (2^N)-1। यही कारण है कि मूल्य सभी बिट्स 1.
+1

क्यों 'ए' और 'बी' के बजाय 'ए' और 'बी' ब्याज से बाहर? सुरक्षा? –

+2

@sgolodetz यह एक अनंत रिकर्सन ('ऑपरेटर |' को कॉल करके परिभाषित किया जा रहा है) दर्ज करेगा। –

+2

@ जोहान्स शैब: तो आप पूर्णांक में एक निहित कलाकार के रूप में + चिह्न का उपयोग कर रहे हैं। इसके बारे में स्पष्ट क्यों न हों और static_cast <>() का उपयोग करें? –

7

सोचा मैं के लिए enum class

FooFlags operator|(FooFlags a, FooFlags b) 
{ 
    typedef std::underlying_type<FooFlags>::type enum_type; 
    return static_cast<FooFlags>(static_cast<enum_type>(a) | static_cast<enum_type>(b)); 
} 

एक C++ 11 संस्करण जोड़ सकते हैं आप 11 संस्करण C++ यदि यह समर्थन करता है, मुझे लगता है कि इस के लिए एक प्रमुख उम्मीदवार होगा है constexpr

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