2010-11-19 7 views
22

Während einige alten C++ - Code überarbeitet, lief ich über mehrere bitflags definiert als enums.Geben Sie sichere (r) Bitflags in C++ ein?

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

Dies ist nicht ungewöhnlich, aber es störte mich, dass, sobald Sie anfangen, Fahnen zu kombinieren, können Sie den Typ-Informationen verlieren.

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

Einige Benutzer auf zeigte, so dass ich nicht die onlyone durch diese gestört bin.

Eine Alternative besteht darin, Flags als #defines oder const-Integrale zu deklarieren, also würden bitweise Operationen den Typ (wahrscheinlich) nicht transformieren. Das Problem dabei ist, dass es erlaubt, dass unser Bit-Set mit nicht verwandten Flags, über Ints oder andere Enums gemischt wird.

Ich bin vertraut mit std::bitset und boost::dynamic_bitset, aber beide sind nicht auf mein Problem zugeschnitten. Was ich suche ist etwas wie C# 's FlagsAttribute.

Meine Frage ist, welche anderen Lösungen gibt es für einen (mehr) Typ sicheren Satz von Bitflags?

Ich poste meine eigene Lösung unten.

Antwort

12

Hier ist meine eigene Lösung, die Elemente von C++ 0x dass die aktuelle Version von VS2010 ermöglicht:

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

Die Schnittstelle nach std::bitset modelliert wird. Mein Ziel war es, dem C++ - Ethos von Typsicherheit und minimalen Overhead (wenn überhaupt) gerecht zu werden. Ich würde jedes Feedback zu meiner Implementierung begrüßen.

Hier ist ein minimales Beispiel:

#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

Gibt es eine Chance für ein Anwendungsbeispiel für Ihre Implementierung? – Eric

+0

@Eric, ich denke, es wäre ziemlich einfach. Was suchen Sie genau? – luke

+0

Ein einfaches Beispiel für das Deklarieren einer Enum'E ', das Instantiieren eines' FlagSet ' und dessen Verwendung. Sicher, ich könnte es schaffen, aber ein Beispiel würde diese Antwort besser machen. – Eric

22

Sie Operatoren für Aufzählungstypen überlasten, die die richtige typisierte Ergebnis zurück.

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

Es sollte beachtet werden, dass theoretisch sicher zu sein, sollten Sie manuell den höchstmöglichen Wert deklarieren, so die Reichweite des Aufzählungstyp ist garantiert alle Kombinationen zu fangen.

  • Eigentlich ist das nicht nötig: Eine Bereich der Aufzählung wird immer in der Lage sein, alle Kombination zu fangen, weil der höchste positive Wert einer Bereichs der Aufzählung immer (2^N)-1 zum ersten N der Lage ist, den höchsten enumerator darzustellen. Dieser Wert hat alle Bits 1.
+1

Warum die '+ a' und' + b' eher als nur 'a' und' b' von Interesse? Sicherheit? –

+2

@sgolodetz würde es eine unendliche Rekursion eingeben (durch Aufrufen des 'Operators |', der definiert wird). –

+2

@Johannes Schaub: SO verwenden Sie das + Zeichen als implizite Umwandlung in Ganzzahl. Warum nicht explizit darüber sprechen und static_cast <>() verwenden? –

7

Thought I für eine C++ 11-Version hinzufügen könnte 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)); 
} 

Wenn Sie 11-Version unterstützt C++ Ich denke, dies ist ein heißer Kandidat sein würde, constexpr

Verwandte Themen