Как использовать перечисления в качестве флагов в C ++? - PullRequest
162 голосов
/ 19 сентября 2009

Обработка enum s как флагов прекрасно работает в C # через атрибут [Flags], но как лучше всего это сделать в C ++?

Например, я хотел бы написать:

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};

seahawk.flags = CanFly | EatsFish | Endangered;

Однако я получаю ошибки компилятора относительно int / enum преобразований. Есть ли лучший способ выразить это, чем просто тупое литье? Предпочтительно, я не хочу полагаться на конструкции из сторонних библиотек, таких как boost или Qt.

РЕДАКТИРОВАТЬ: Как указано в ответах, я могу избежать ошибки компилятора, объявив seahawk.flags как int. Тем не менее, я хотел бы иметь некоторый механизм для обеспечения безопасности типов, поэтому кто-то не может написать seahawk.flags = HasMaximizeButton.

Ответы [ 19 ]

5 голосов
/ 19 сентября 2009

Если ваш компилятор еще не поддерживает строго типизированные перечисления, вы можете посмотреть следующую статью из источника c ++:

Из аннотации:

В этой статье представлено решение проблемы ограничения битовых операций
. разрешить только безопасные и законные, и превратить все недействительные битовые манипуляции в ошибки во время компиляции. Лучше всего, синтаксис битовых операций остается неизменным, и код, работающий с битами, не нуждается в изменении, кроме, возможно, исправить ошибки, которые еще не были обнаружены.

3 голосов
/ 06 августа 2016

Вот вариант для битовых масок, если вы на самом деле не используете отдельные значения перечисления (например, вам не нужно их отключать) ... и если вы не беспокоитесь о поддержке бинарной совместимости то есть: вам все равно, где ваши биты живут ... что вы, вероятно,. Кроме того, вы не должны быть слишком обеспокоены областью видимости и контролем доступа. Хммм, перечисления имеют некоторые хорошие свойства для битовых полей ... интересно, кто-нибудь когда-либо пробовал это:)

struct AnimalProperties
{
    bool HasClaws : 1;
    bool CanFly : 1;
    bool EatsFish : 1;
    bool Endangered : 1;
};

union AnimalDescription
{
    AnimalProperties Properties;
    int Flags;
};

void TestUnionFlags()
{
    AnimalDescription propertiesA;
    propertiesA.Properties.CanFly = true;

    AnimalDescription propertiesB = propertiesA;
    propertiesB.Properties.EatsFish = true;

    if( propertiesA.Flags == propertiesB.Flags )
    {
        cout << "Life is terrible :(";
    }
    else
    {
        cout << "Life is great!";
    }

    AnimalDescription propertiesC = propertiesA;
    if( propertiesA.Flags == propertiesC.Flags )
    {
        cout << "Life is great!";
    }
    else
    {
        cout << "Life is terrible :(";
    }
}

Мы можем видеть, что жизнь прекрасна, у нас есть свои дискретные ценности, и у нас есть хороший int для & и | к нашему сердцу содержание, которое все еще имеет контекст того, что означают его биты. Все непротиворечиво и предсказуемо ... для меня ... пока я продолжаю использовать компилятор Microsoft VC ++ с обновлением 3 на Win10 x64 и не трогаю флаги моего компилятора:)

Несмотря на то, что все замечательно ... у нас есть некоторый контекст относительно значения флагов, поскольку он находится в единстве с битовым полем в ужасном реальном мире, где может быть ответственна ваша программа для более чем одной отдельной задачи вы все равно можете случайно (довольно легко) разбить два поля флагов различных объединений (скажем, AnimalProperties и ObjectProperties, так как они оба целые), смешивая все ваши биты, что является ужасной ошибкой для проследить ... и, насколько я знаю, многие люди в этом посте не очень часто работают с битовыми масками, поскольку создавать их легко, а поддерживать их сложно.

class AnimalDefinition {
public:
    static AnimalDefinition *GetAnimalDefinition( AnimalFlags flags );   //A little too obvious for my taste... NEXT!
    static AnimalDefinition *GetAnimalDefinition( AnimalProperties properties );   //Oh I see how to use this! BORING, NEXT!
    static AnimalDefinition *GetAnimalDefinition( int flags ); //hmm, wish I could see how to construct a valid "flags" int without CrossFingers+Ctrl+Shift+F("Animal*"). Maybe just hard-code 16 or something?

    AnimalFlags animalFlags;  //Well this is *way* too hard to break unintentionally, screw this!
    int flags; //PERFECT! Nothing will ever go wrong here... 
    //wait, what values are used for this particular flags field? Is this AnimalFlags or ObjectFlags? Or is it RuntimePlatformFlags? Does it matter? Where's the documentation? 
    //Well luckily anyone in the code base and get confused and destroy the whole program! At least I don't need to static_cast anymore, phew!

    private:
    AnimalDescription m_description; //Oh I know what this is. All of the mystery and excitement of life has been stolen away :(
}

Итак, вы делаете свою декларацию объединения закрытой, чтобы предотвратить прямой доступ к «Флагам», и должны добавить геттеры / сеттеры и перегрузки операторов, а затем сделать макрос для всего этого, и вы в основном вернулись к тому, с чего начали Вы пытались сделать это с помощью Enum.

К сожалению, если вы хотите, чтобы ваш код был переносимым, я не думаю, что есть какой-либо способ либо A) гарантировать битовую компоновку, либо B) определить битовую компоновку во время компиляции (чтобы вы могли отслеживать и, по крайней мере, исправлять) для изменений между версиями / платформами и т. д.) Смещение в структуре с битовыми полями

Во время выполнения вы можете разыгрывать трюки с установкой полей и установкой флагов XOR, чтобы увидеть, какие биты изменились, для меня это звучит довольно глупо, хотя стихи имеют 100% согласованное, независимое от платформы и полностью детерминированное решение, например: ENUM .

TL; DR: Не слушай ненавистников. C ++ не английский. Тот факт, что буквальное определение сокращенного ключевого слова, унаследованного от C, может не подходить для вашего использования, не означает, что вы не должны использовать его, когда определение ключевого слова C и C ++ абсолютно включает ваш сценарий использования. Вы также можете использовать структуры, чтобы моделировать вещи, отличные от структур, и классы для вещей, отличных от школьной и социальной касты. Вы можете использовать float для значений, которые обоснованы. Вы можете использовать char для переменных, которые не являются ни сожженными, ни людьми в романе, пьесе или фильме. Любой программист, который идет в словарь, чтобы определить значение ключевого слова до того, как спецификация языка ... ну, я буду там держать язык за зубами.

Если вы хотите, чтобы ваш код был смоделирован на разговорном языке, вам лучше было бы писать в Objective-C, который, между прочим, также активно использует перечисления для битовых полей.

3 голосов
/ 26 сентября 2014

Вы путаете объекты и коллекции объектов. В частности, вы путаете двоичные флаги с наборами двоичных флагов. Правильное решение будет выглядеть так:

// These are individual flags
enum AnimalFlag // Flag, not Flags
{
    HasClaws = 0,
    CanFly,
    EatsFish,
    Endangered
};

class AnimalFlagSet
{
    int m_Flags;

  public:

    AnimalFlagSet() : m_Flags(0) { }

    void Set( AnimalFlag flag ) { m_Flags |= (1 << flag); }

    void Clear( AnimalFlag flag ) { m_Flags &= ~ (1 << flag); }

    bool Get( AnimalFlag flag ) const { return (m_Flags >> flag) & 1; }

};
3 голосов
/ 04 апреля 2017

Я бы хотел остановиться на ответе Uliwitness , исправляя его код для C ++ 98 и используя Safe Bool idiom , из-за отсутствия шаблона std::underlying_type<> и explicit ключевое слово в версиях C ++ ниже C ++ 11.

Я также изменил его, чтобы значения перечисления могли быть последовательными без какого-либо явного присваивания, чтобы вы могли иметь

enum AnimalFlags_
{
    HasClaws,
    CanFly,
    EatsFish,
    Endangered
};
typedef FlagsEnum<AnimalFlags_> AnimalFlags;

seahawk.flags = AnimalFlags() | CanFly | EatsFish | Endangered;

Затем вы можете получить значение необработанных флагов с помощью

seahawk.flags.value();

Вот код.

template <typename EnumType, typename Underlying = int>
class FlagsEnum
{
    typedef Underlying FlagsEnum::* RestrictedBool;

public:
    FlagsEnum() : m_flags(Underlying()) {}

    FlagsEnum(EnumType singleFlag):
        m_flags(1 << singleFlag)
    {}

    FlagsEnum(const FlagsEnum& original):
        m_flags(original.m_flags)
    {}

    FlagsEnum& operator |=(const FlagsEnum& f) {
        m_flags |= f.m_flags;
        return *this;
    }

    FlagsEnum& operator &=(const FlagsEnum& f) {
        m_flags &= f.m_flags;
        return *this;
    }

    friend FlagsEnum operator |(const FlagsEnum& f1, const FlagsEnum& f2) {
        return FlagsEnum(f1) |= f2;
    }

    friend FlagsEnum operator &(const FlagsEnum& f1, const FlagsEnum& f2) {
        return FlagsEnum(f1) &= f2;
    }

    FlagsEnum operator ~() const {
        FlagsEnum result(*this);
        result.m_flags = ~result.m_flags;
        return result;
    }

    operator RestrictedBool() const {
        return m_flags ? &FlagsEnum::m_flags : 0;
    }

    Underlying value() const {
        return m_flags;
    }

protected:
    Underlying  m_flags;
};
2 голосов
/ 19 сентября 2009

Как указано выше (Кай) или выполните следующие действия. Действительно перечисления являются «перечислениями», то, что вы хотите сделать, это иметь набор, поэтому вы должны действительно использовать stl :: set

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};

int main(void)
{
    AnimalFlags seahawk;
    //seahawk= CanFly | EatsFish | Endangered;
    seahawk= static_cast<AnimalFlags>(CanFly | EatsFish | Endangered);
}
2 голосов
/ 09 октября 2018

В настоящее время языковая поддержка для флагов перечисления отсутствует, мета-классы по своей природе могут добавить эту функцию, если она когда-либо будет частью стандарта c ++.

Мое решение состояло бы в том, чтобы создать экземпляры шаблонных функций только для перечисления, добавив поддержку побитовых безопасных операций для класса перечисления, используя его базовый тип:

Файл: EnumClassBitwise.h

#pragma once
#ifndef _ENUM_CLASS_BITWISE_H_
#define _ENUM_CLASS_BITWISE_H_

#include <type_traits>

//unary ~operator    
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum& operator~ (Enum& val)
{
    val = static_cast<Enum>(~static_cast<std::underlying_type_t<Enum>>(val));
    return val;
}

// & operator
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator& (Enum lhs, Enum rhs)
{
    return static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) & static_cast<std::underlying_type_t<Enum>>(rhs));
}

// &= operator
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator&= (Enum& lhs, Enum rhs)
{
    lhs = static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) & static_cast<std::underlying_type_t<Enum>>(rhs));
    return lhs;
}

//| operator

template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator| (Enum lhs, Enum rhs)
{
    return static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) | static_cast<std::underlying_type_t<Enum>>(rhs));
}
//|= operator

template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum& operator|= (Enum& lhs, Enum rhs)
{
    lhs = static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) | static_cast<std::underlying_type_t<Enum>>(rhs));
    return lhs;
}

#endif // _ENUM_CLASS_BITWISE_H_

Для удобства и для уменьшения ошибок вы можете захотеть обернуть свои операции битовых флагов для перечислений и целых чисел:

Файл: BitFlags.h

#pragma once
#ifndef _BIT_FLAGS_H_
#define _BIT_FLAGS_H_

#include "EnumClassBitwise.h"

 template<typename T>
 class BitFlags
 {
 public:

     constexpr inline BitFlags() = default;
     constexpr inline BitFlags(T value) { mValue = value; }
     constexpr inline BitFlags operator| (T rhs) const { return mValue | rhs; }
     constexpr inline BitFlags operator& (T rhs) const { return mValue & rhs; }
     constexpr inline BitFlags operator~ () const { return ~mValue; }
     constexpr inline operator T() const { return mValue; }
     constexpr inline BitFlags& operator|=(T rhs) { mValue |= rhs; return *this; }
     constexpr inline BitFlags& operator&=(T rhs) { mValue &= rhs; return *this; }
     constexpr inline bool test(T rhs) const { return (mValue & rhs) == rhs; }
     constexpr inline void set(T rhs) { mValue |= rhs; }
     constexpr inline void clear(T rhs) { mValue &= ~rhs; }

 private:
     T mValue;
 };
#endif //#define _BIT_FLAGS_H_

Возможное использование:

#include <cstdint>
#include <BitFlags.h>
void main()
{
    enum class Options : uint32_t
    { 
          NoOption = 0 << 0
        , Option1  = 1 << 0
        , Option2  = 1 << 1
        , Option3  = 1 << 2
        , Option4  = 1 << 3
    };

    const uint32_t Option1 = 1 << 0;
    const uint32_t Option2 = 1 << 1;
    const uint32_t Option3 = 1 << 2;
    const uint32_t Option4 = 1 << 3;

   //Enum BitFlags
    BitFlags<Options> optionsEnum(Options::NoOption);
    optionsEnum.set(Options::Option1 | Options::Option3);

   //Standard integer BitFlags
    BitFlags<uint32_t> optionsUint32(0);
    optionsUint32.set(Option1 | Option3); 

    return 0;
}
2 голосов
/ 01 ноября 2014

Вот мое решение без какой-либо перегрузки или приведения:

namespace EFoobar
{
    enum
    {
        FB_A    = 0x1,
        FB_B    = 0x2,
        FB_C    = 0x4,
    };
    typedef long Flags;
}

void Foobar(EFoobar::Flags flags)
{
    if (flags & EFoobar::FB_A)
        // do sth
        ;
    if (flags & EFoobar::FB_B)
        // do sth
        ;
}

void ExampleUsage()
{
    Foobar(EFoobar::FB_A | EFoobar::FB_B);
    EFoobar::Flags otherflags = 0;
    otherflags|= EFoobar::FB_B;
    otherflags&= ~EFoobar::FB_B;
    Foobar(otherflags);
}

Я думаю, что это нормально, потому что мы в любом случае идентифицируем (не строго типизированные) перечисления и целые числа.

Так же, как (более длинное) примечание, если вы

  • хотите использовать строго типизированные перечисления и
  • не нужно много играть со своими флагами
  • производительность не является проблемой

Я бы придумал это:

#include <set>

enum class EFoobarFlags
{
    FB_A = 1,
    FB_B,
    FB_C,
};

void Foobar(const std::set<EFoobarFlags>& flags)
{
    if (flags.find(EFoobarFlags::FB_A) != flags.end())
        // do sth
        ;
    if (flags.find(EFoobarFlags::FB_B) != flags.end())
        // do sth
        ;
}

void ExampleUsage()
{
    Foobar({EFoobarFlags::FB_A, EFoobarFlags::FB_B});
    std::set<EFoobarFlags> otherflags{};
    otherflags.insert(EFoobarFlags::FB_B);
    otherflags.erase(EFoobarFlags::FB_B);
    Foobar(otherflags);
}

с использованием списков инициализаторов C ++ 11 и enum class.

1 голос
/ 31 августа 2018

Может быть, как NS_OPTIONS Objective-C.

#define ENUM(T1, T2) \
enum class T1 : T2; \
inline T1 operator~ (T1 a) { return (T1)~(int)a; } \
inline T1 operator| (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) | static_cast<T2>(b))); } \
inline T1 operator& (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) & static_cast<T2>(b))); } \
inline T1 operator^ (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) ^ static_cast<T2>(b))); } \
inline T1& operator|= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) |= static_cast<T2>(b))); } \
inline T1& operator&= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) &= static_cast<T2>(b))); } \
inline T1& operator^= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) ^= static_cast<T2>(b))); } \
enum class T1 : T2

ENUM(Options, short) {
    FIRST  = 1 << 0,
    SECOND = 1 << 1,
    THIRD  = 1 << 2,
    FOURTH = 1 << 3
};

auto options = Options::FIRST | Options::SECOND;
options |= Options::THIRD;
if ((options & Options::SECOND) == Options::SECOND)
    cout << "Contains second option." << endl;
if ((options & Options::THIRD) == Options::THIRD)
    cout << "Contains third option." << endl;
return 0;

// Output:
// Contains second option. 
// Contains third option.
0 голосов
/ 22 февраля 2018

Только синтаксический сахар. Никаких дополнительных метаданных.

namespace UserRole // grupy
{ 
    constexpr uint8_t dea = 1;
    constexpr uint8_t red = 2;
    constexpr uint8_t stu = 4;
    constexpr uint8_t kie = 8;
    constexpr uint8_t adm = 16;
    constexpr uint8_t mas = 32;
}

Операторы флагов на целочисленном типе просто работают.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...