Frage

Während ich einen alten C ++ - Code überarbeitete, bin ich auf mehrere gestoßen Bitflags definiert als Enums.

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

Dies ist nicht ungewöhnlich, aber es störte mich, dass Sie die Typinformationen verlieren, sobald Sie anfangen, Flaggen zu kombinieren.

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

Einige suchen auf, zeigten, dass ich nicht der bin nur eines von diesem belästigt.

Eine Alternative besteht darin, Flags als #defines oder const -Integrale zu deklarieren, sodass bitweise Operationen den Typ (wahrscheinlich) nicht transformieren würden. Das Problem dabei ist, dass unser Bit über Inten oder andere Aufzüge mit nicht verwandten Flaggen eingestellt ist.

Ich bin vertraut mit std :: bitset und Boost :: Dynamic_bitset, aber auch nicht so konzipiert, dass ich mein Problem angehen soll. Was ich suche, ist so etwas wie C#'s Flagsattribute.

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

Ich werde unten meine eigene Lösung veröffentlichen.

War es hilfreich?

Lösung 2

Hier ist meine eigene Lösung, bei der Elemente von C ++ 0x verwendet werden, die 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 ist nachempfunden std :: bitset. Mein Ziel war es, dem C ++ - Ethos der Typensicherheit und der minimalen (falls vorhandenen) Overhead treu zu bleiben. 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;
}

Andere Tipps

Sie können Operatoren für Aufzählungstypen überlasten, die das ordnungsgemäß getippte Ergebnis zurückgeben.

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

Es ist zu beachten, dass Sie theoretisch sicher den höchstmöglichen Wert deklarieren sollten, damit der Bereich des Aufzählungstyps garantiert alle Kombinationen fängt.

  • Eigentlich ist das nicht erforderlich: Der Bereich einer Aufzählung kann immer in der Lage sein, alle Kombination zu fangen, da der höchste positive Wert des Reichweite einer Aufzählung immer ist (2^N)-1 zum ersten N in der Lage zu sein, den höchsten Aufzähler darzustellen. Dieser Wert hat alle Bits 1.

Ich dachte, ich könnte eine C ++ 11 -Version für hinzufügen 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 C ++ 11 Version unterstützen, denke ich, dass dies ein Hauptkandidat ist constexpr

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top