Question

Tout en révisant un vieux code C ++, j'ai rencontré plusieurs bitflags défini comme des énumérations.

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

Ce n'est pas rare, mais cela m'a dérangé 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*

Certains recherchaient donc que je ne suis pas le seulement une gêné par cela.

Une alternative consiste à déclarer les drapeaux sous le nom de #Defines ou Const Integrals, donc les opérations bitwise ne transformeraient pas le type (probablement). Le problème avec cela est qu'il permet à notre ensemble de bits de se combler avec des drapeaux non apparentés, via INTS ou d'autres enums.

Je connais std :: bitset et boost :: dynamic_bitset, mais aucun n'est conçu pour résoudre mon problème. Ce que je cherche est quelque chose comme C # FlagsAttribute.

Ma question est, quelles autres solutions existent-elles pour un ensemble sûr de type (plus) de BitFlags?

Je publierai ma propre solution ci-dessous.

Était-ce utile?

La solution 2

Voici 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 modélisée après std :: bitset. Mon objectif était d'être fidèle à l'éthique C ++ de la sécurité de type et des frais généraux minimaux (le cas échéant). J'accueillerai tous les 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;
}

Autres conseils

Vous pouvez surcharger les opérateurs pour les types d'énumération qui renvoient le résultat tapé 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, de sorte que la plage du type d'énumération est garantie pour attraper toutes les combinaisons.

  • En fait, cela n'est pas nécessaire: la gamme d'une énumération sera toujours en mesure d'attraper toute la combinaison, car la valeur positive la plus élevée de la gamme d'une énumération est toujours (2^N)-1 pour le premier N être capable de représenter le plus haut énumérateur. Cette valeur a tous les bits 1.

Je pensais que je pourrais ajouter une version C ++ 11 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 votre version C ++ 11 le prend en charge, je suppose que ce serait un candidat principal pour constexpr

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top