2010-11-19 30 views
22

Lors de la révision d'un ancien code C++, j'ai rencontré plusieurs bitflags définis comme enums.Tapez-vous des drapeaux (r) sûrs en C++?

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

Ce n'est pas rare, mais il me dérangeait que dès que vous commencez à combiner des drapeaux, vous perdez les informations de type.

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

quelques recherches sur SO montré que je ne suis pas le onlyone dérangé par cela.

Une alternative consiste à déclarer des indicateurs comme des #defines ou des intégrales const, de sorte que les opérations au niveau des bits ne transformeraient pas le type (probablement). Le problème avec ceci est qu'il permet à notre ensemble de bits de se mélanger avec des drapeaux sans rapport, via ints ou d'autres énumérations. Je connais std::bitset et boost::dynamic_bitset, mais aucune d'entre elles n'est conçue pour résoudre mon problème. Ce que je cherche, c'est quelque chose comme FlagsAttribute de C#.

Ma question est, quelles autres solutions sont là pour un (plus) type de coffre-fort de bitflags?

Je posterai ma propre solution ci-dessous.

Répondre

12

Voilà ma propre solution, en utilisant des éléments de C++ 0x que la version actuelle de VS2010 permet:

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

L'interface est calquée sur std::bitset. Mon but était d'être fidèle à l'éthos C++ de la sécurité de type et des frais généraux minimes (le cas échéant). Je serais heureux de recevoir des commentaires sur ma mise en œuvre.

Voici un exemple minimal:

#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

Une chance d'exemple d'utilisation pour votre implémentation? – Eric

+0

@Eric, je pense que ce serait plutôt simple. Que recherchez-vous exactement? – luke

+0

Un exemple simple de déclaration d'une énumération 'E', l'instanciation d'un' FlagSet ', et son utilisation. Bien sûr, je pourrais y arriver, mais un exemple rendrait cette réponse meilleure. – Eric

22

Vous pouvez surcharger les opérateurs pour les types d'énumération qui renvoient le résultat typé approprié.

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

Il convient de noter que, pour être théoriquement sûr, vous devez déclarer manuellement la valeur la plus élevée possible pour la gamme du type d'énumération est garanti pour attraper toutes les combinaisons.

  • En fait, ce n'est pas nécessaire: la gamme Une énumération sera toujours en mesure de prendre toutes les combinaisons, parce que la plus grande valeur positive de la gamme d'une énumération est toujours (2^N)-1 pour la première N étant en mesure de représenter le plus haut recenseur. Cette valeur a tous les bits 1.
+1

Pourquoi les '+ a' et' + b' plutôt que juste 'a' et' b' par intérêt? Sécurité? –

+2

@sgolodetz entrerait une récursion infinie (en appelant 'l'opérateur |' en cours de définition). –

+2

@Johannes Schaub: SO vous utilisez le signe + comme une distribution implicite en nombre entier. Pourquoi ne pas être explicite à ce sujet et utiliser static_cast <>()? –

7

Je pensais que je pourrais ajouter une C++ 11 version pour 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)); 
} 

Si vous C++ 11 prend en charge la version, je suppose que ce serait un candidat de choix pour constexpr