2010-11-19 11 views
22

Si bien la revisión de un viejo código C++, me encontré con varios bitflags definido como enumeraciones.Tipo safe (r) bitflags en C++?

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

Esto no es raro, pero me molesta que tan pronto como comienzas a combinar banderas, pierdes la información del tipo.

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

algunas búsquedas en SO mostró que yo no soy el onlyone molestado por esto.

Una alternativa es declarar como banderas o #defines integrales const, por lo que las operaciones bit a bit no se transformarían el tipo (probablemente). El problema con esto es que permite que nuestro bit se mezcle con banderas no relacionadas, a través de ints u otras enums.

Estoy familiarizado con std::bitset y boost::dynamic_bitset, pero ninguno está diseñado para solucionar mi problema. Lo que estoy buscando es algo así como C# 's FlagsAttribute.

Mi pregunta es, ¿qué otras soluciones están ahí por una (más) de tipo establecidos segura de bitflags?

voy a publicar mi propia solución a continuación.

Respuesta

12

Aquí es mi propia solución, utilizando elementos de C++ 0x que la versión actual de VS2010 permite:

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

La interfaz es el modelo de std::bitset. Mi objetivo era ser fiel al espíritu de c + + de la seguridad de tipo y a una sobrecarga mínima (si la hubiera). Me gustaría recibir cualquier comentario sobre mi implementación.

Aquí está un ejemplo mínimo:

#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

Cualquier posibilidad de un ejemplo de uso para su aplicación? – Eric

+0

@Eric, siento que sería bastante directo. ¿Qué estás buscando exactamente? – luke

+0

Un ejemplo sencillo de declarar una enumeración 'E', instancias de un' 'Flagset , y su uso. Claro, podría resolverlo, pero un ejemplo mejoraría esta respuesta. – Eric

22

Puede sobrecargar operadores para tipos de enumeración que devuelven el resultado correcto con tipo.

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

Cabe señalar que para ser teóricamente seguro, debe declarar manualmente el valor más alto posible para que la estufa del tipo de enumeración está garantizado para atrapar todas las combinaciones.

  • hecho de que no se necesita: rango de una enumeración siempre será capaz de atrapar todas las combinaciones, ya que el valor positivo más alto de la gama de una enumeración es siempre (2^N)-1 por primera N ser capaz de representar la más alta empadronador. Ese valor tiene todos los bits 1.
+1

¿Por qué el '+ a' y' + b' en lugar de simplemente 'a' y' b' fuera de interés? ¿La seguridad? –

+2

@sgolodetz entraría en una recursión infinita (llamando al 'operador |' que se está definiendo). –

+2

@Johannes Schaub: ASÍ QUE estás usando el signo + como un molde implícito a entero. ¿Por qué no ser explícito al respecto y usar static_cast <>()? –

7

pensé que podría añadir un C++ versión 11 para 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 C++ 11 versión es compatible con lo que supongo que esto sería un candidato ideal para constexpr