c++ enums c++11 type-safety bitflags

Escriba safe(r) bitflags en C++?



enums c++11 (3)

Aquí está 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 está modelada después de std::bitset . Mi objetivo era ser fiel al espíritu de c ++ del tipo de seguridad y la sobrecarga mínima (si corresponde). Agradecería cualquier comentario sobre mi implementación.

Aquí hay 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; }

Mientras revisaba algunos códigos antiguos de c ++, encontré varias bitflags definidas como enumeraciones.

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

Esto no es raro, pero me molestó que tan pronto como empieces a combinar banderas, pierdas la información de 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 demostraron que no soy la only molesta por esto.

Una alternativa es declarar indicadores como #defines o const integrales, de modo que las operaciones a nivel de bits no transformarían el tipo (probablemente). El problema con esto es que permite que nuestro conjunto de bits se mezcle con indicadores no relacionados, a través de ints u otras enumeraciones.

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

Mi pregunta es, ¿qué otras soluciones existen para un (más) conjunto seguro de bitflags?

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


Pensé que podría agregar una versión c ++ 11 para la 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 la versión c ++ 11 lo admite, supongo que este sería un candidato principal para constexpr


Puede sobrecargar los operadores para los tipos de enumeración que devuelven el resultado escrito correcto.

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

Debe tenerse en cuenta que, para ser teóricamente seguro, debe declarar manualmente el valor más alto posible para que el rango del tipo de enumeración garantice todas las combinaciones.

  • En realidad, eso no es necesario: el rango de una enumeración siempre podrá capturar todas las combinaciones, porque el valor positivo más alto del rango de una enumeración es siempre (2^N)-1 para que la primera N pueda representar al enumerador más alto. Ese valor tiene todos los bits 1.