Як використовувати переписки як прапори в C ++?


187

Трактування enums як прапорів чудово працює в C # через [Flags]атрибут, але який найкращий спосіб зробити це в C ++?

Наприклад, я хотів би написати:

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

seahawk.flags = CanFly | EatsFish | Endangered;

Однак я отримую помилки компілятора щодо int/ enumперетворень. Чи є кращий спосіб висловити це, ніж просто тупий кастинг? Переважно, я не хочу покладатися на конструкції із сторонніх бібліотек, такі як boost або Qt.

EDIT: Як зазначено у відповідях, я можу уникнути помилки компілятора, оголосивши seahawk.flagsяк int. Однак я хотів би мати якийсь механізм забезпечення безпеки типу, тому хтось не може писати seahawk.flags = HasMaximizeButton.


Наскільки я знаю у Visual C ++ 2013, [Flags]атрибут працює чудово, тобто:[Flags] enum class FlagBits{ Ready = 1, ReadMode = 2, WriteMode = 4, EOF = 8, Disabled = 16};
rivanov

@rivanov, Ні, це не працює з C ++ (також 2015). Ви мали на увазі C #?
Аджай

5
@rivanov, атрибут [Flags] працює лише з .Net Framework в C ++ CLI, нативний C ++ не підтримує такі атрибути.
Золтан Тірінда

Відповіді:


250

"Правильним" способом є визначення бітових операторів для enum, як:

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

inline AnimalFlags operator|(AnimalFlags a, AnimalFlags b)
{
    return static_cast<AnimalFlags>(static_cast<int>(a) | static_cast<int>(b));
}

Решта решти бітових операторів. Змініть за необхідності, якщо діапазон перерахунку перевищує діапазон int.


42
^ це. Питання лише в тому, як автоматизувати / шаблонувати визначення операторів, так що вам не доведеться постійно визначати їх щоразу, коли ви додаєте новий перелік.
eodabash

10
Крім того, чи є примірник з довільного int назад до типу enum дійсним, навіть якщо значення int не відповідає жодному з ідентифікаторів enum?
Інго Шальк-Шупп

8
Це повна нісенітниця. Який член AnimalFlagsпредставлений виразом HasClaws | CanFly? Це не для чого enum. Використовуйте цілі числа та константи.
Гонки легкості на орбіті

26
@LightnessRacesinOrbit: Це не правильно. Домен типу enum є доменом його базового типу - лише певним дано ім’я. І щоб відповісти на ваше запитання: Член " (HasClaws | CanFly)".
Xeo

5
@MarcusJ: обмеження ваших значень 2 повноваженнями дозволяє вам використовувати переліки як бітові прапори. Таким чином, якщо ви отримаєте 3, ви знаєте, що це і HasClaws(= 1), і CanFly(= 2). Якщо замість цього ви просто призначите значення від 1 до 4 прямо наскрізь, і ви отримаєте 3, це може бути одинарне EatsFish, або знову ж таки комбінація HasClawsі CanFly. Якщо ваше перерахування позначає виключні стани, тоді послідовні значення є нормальними, але комбінація прапорів потребує, щоб ці значення були ексклюзивними.
Крістіан Северин

122

Примітка (також трохи поза темою): Ще один спосіб створення унікальних прапорів можна виконати за допомогою трохи зрушення. Мені самому таке легше читати.

enum Flags
{
    A = 1 << 0, // binary 0001
    B = 1 << 1, // binary 0010
    C = 1 << 2, // binary 0100
    D = 1 << 3, // binary 1000
};

Він може містити значення до int, тобто більшу частину часу, 32 прапори, що чітко відображається на величині зсуву.


2
Чи можете ви видалити останню кому (3,) та додати двокрапку після}, щоб зробити код легко копіювати та вставляти? Спасибі
Кату

4
Ніякої згадки про гексидцимальний? Богохульство!
Фарап

1
@Jamie, кардинали завжди починаються з 1, лише розпорядження можуть починатися з 0 або 1, залежно від того, з ким ви говорите.
Майкл

2
@Michael, це правда! Як правило, ви зазвичай резервуєте 0 для BLAH_NONE. :-) Дякую за те, що я пам’ятаю!
Джеймі

1
@Katu • зайва кома підсумкового перерахування дозволена стандартом. Мені це не подобається, але я вже знаю, що сказав би мені Струструп ... "Вам це не подобається? Добре сміливо створюйте свою мову. Я це зробив".
Eljay

55

Для ледачих людей, як я, тут розроблено шаблонне рішення для копіювання та вставки:

template<class T> inline T operator~ (T a) { return (T)~(int)a; }
template<class T> inline T operator| (T a, T b) { return (T)((int)a | (int)b); }
template<class T> inline T operator& (T a, T b) { return (T)((int)a & (int)b); }
template<class T> inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); }
template<class T> inline T& operator|= (T& a, T b) { return (T&)((int&)a |= (int)b); }
template<class T> inline T& operator&= (T& a, T b) { return (T&)((int&)a &= (int)b); }
template<class T> inline T& operator^= (T& a, T b) { return (T&)((int&)a ^= (int)b); }

23
+1 Лінь - одна з трьох великих чеснот програміста: threevirtues.com
Pharap

10
Це дуже приємне рішення, просто будьте уважні, що воно з задоволенням надасть побітові операції для будь-якого типу. Я використовую щось подібне, але з додаванням ознак, що ідентифікують типи, до яких я хочу застосувати це в поєднанні з невеликою магією enable_if.
Рай

@Rai: ви завжди можете розмістити його в просторі імен і, usingде це доречно, просто так rel_ops.
Яків Галка

1
@ybungalobill, але у вас все ще буде така ж проблема з операціями, що застосовуються до будь-якого типу в області використання, яка, імовірно, відповідала б перерахунку? Я думаю, що риси, швидше за все, необхідні.
Рай

19
Не використовуйте цей код. Це відкриває двері для будь-якого класу, яким можна керувати помилково. Також використовується код старого стилю, який не пройде через сувору компіляцію GCC shitalshah.com/p/… .
Шітал Шах

44

Зверніть увагу, якщо ви працюєте в середовищі Windows, DEFINE_ENUM_FLAG_OPERATORSу winnt.h визначений макрос, який виконує цю роботу. Тож у цьому випадку ви можете це зробити:

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

seahawk.flags = CanFly | EatsFish | Endangered;

44

Який тип змінної seahawk.flags?

У стандартних C ++ перерахування не є безпечними для типу. Вони є фактично цілими числами.

AnimalFlags НЕ повинен бути типом вашої змінної. Ваша змінна повинна бути int, і помилка піде.

Вводити шістнадцяткові значення, як і деякі інші люди, запропоновані, не потрібно. Це не має ніякої різниці.

Значення перерахунку ARE типу int за замовчуванням. Таким чином, ви можете, безумовно, побіжно АБО комбінувати їх і складати їх і зберігати результат у цілій формі.

Тип enum - це обмежений підмножина int, значення якого є одним із його перерахованих значень. Отже, коли ви робите якесь нове значення поза цим діапазоном, ви не можете його призначити без викидання змінної вашого типу enum.

Ви також можете змінити типи значень enum, якщо хочете, але в цьому питанні немає сенсу.

EDIT: Плакат сказав, що вони стурбовані безпекою типу, і вони не хочуть значення, яке не повинно існувати всередині типу int.

Але було б небезпечно ввести значення поза діапазону AnimalFlags всередині змінної типу AnimalFlags.

Існує безпечний спосіб перевірити значення діапазону, хоча всередині типу int ...

int iFlags = HasClaws | CanFly;
//InvalidAnimalFlagMaxValue-1 gives you a value of all the bits 
// smaller than itself set to 1
//This check makes sure that no other bits are set.
assert(iFlags & ~(InvalidAnimalFlagMaxValue-1) == 0);

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

    // put new enum values above here
    InvalidAnimalFlagMaxValue = 16
};

Вищезазначене не заважає вам поставити недійсний прапор з іншого перерахунку, який має значення 1,2,4 або 8.

Якщо ви хочете абсолютної безпеки, тоді ви можете просто створити std :: set і зберегти там кожен прапор. Це не просторовий простір, але він безпечний для типу і надає вам таку ж здатність, що і Bitflag Int.

C ++ 0x примітка: сильно набрані переписки

У C ++ 0x ви, нарешті, можете ввести значення безпечних перерахувань ....

enum class AnimalFlags {
    CanFly = 2,
    HasClaws = 4
};

if(CanFly == 2) { }//Compiling error

4
Значення enum не є цілими числами, але вони дуже легко перетворюються на цілі числа. Тип HasClaws | CanFlyє деяким цілим типом, але тип HasClawsє AnimalFlags, а не цілим числом.
Кару

1
Ах, але що робити, якщо ми визначимо правильний діапазон перерахунку не лише окремими значеннями прапора, а й їх розрядними комбінаціями. Тоді відповідь ейдолона правильна і стверджує, що лише такі комбінації правильних переліків прапора можуть передаватися як цей тип.
Скотт

3
@Scott: Варто зазначити, що стандарт C ++ визначає дійсний діапазон значень екземпляра enum таким чином. "для перерахунку, де emin - найменший нумератор, а emax - найбільший, значення перерахування - це значення в діапазоні bmin до bmax, визначені таким чином: Нехай K буде 1 для представлення комплементу двох та 0 для one" представлення доповнення або значущої величини. bmax - найменше значення, яке перевищує або дорівнює max(|emin| − K, |emax|)і дорівнює (1u<<M) - 1, де M- це негативне ціле число ".
Ben Voigt

Для тих, хто (як я) просто хочуть чогось практичного, що дозволяє розряджати значеннями перерахунків і не виглядати занадто некрасиво з шаблонами та типом кастингу, це хороше рішення; просто визначте змінні для типу int.
Ерік Соколовський

Також зауважте, що в C ++ регулярний enumтехнічно не за замовчуванням intє його базовим типом (або до-С ++ 11 (IIRC), або після-С ++ 11, коли не вказаний базовий тип), хоча enum class це так . Натомість базовий тип за замовчуванням встановлює щось достатньо велике, щоб представити всі перелічувачі, з єдиним реальним жорстким правилом, яке є лише більшим, ніж intякщо явно має бути. В основному, базовий тип визначається як (перефразоване) "все, що працює, але це, мабуть, int хіба що нумератори занадто великі для int".
Час Джастіна -

26

Я вважаю, що прийнята в даний час відповідь від eidolon занадто небезпечною. Оптимізатор компілятора може робити припущення про можливі значення в перерахунку, і ви можете отримати сміття з недійсними значеннями. І зазвичай ніхто не хоче визначати всі можливі перестановки в прапорцях.

Як зазначає Брайан Р. Бонді нижче, якщо ви використовуєте C ++ 11 (який повинен кожен, це добре), тепер це можна зробити легше enum class:

enum class ObjectType : uint32_t
{
    ANIMAL = (1 << 0),
    VEGETABLE = (1 << 1),
    MINERAL = (1 << 2)
};


constexpr enum ObjectType operator |( const enum ObjectType selfValue, const enum ObjectType inValue )
{
    return (enum ObjectType)(uint32_t(selfValue) | uint32_t(inValue));
}

// ... add more operators here. 

Це забезпечує стабільний діапазон розмірів і значень, визначаючи тип для перерахунку, гальмує автоматичне перекидання перерахунків на інти тощо, використовуючи enum class, і використовує constexprдля того, щоб код операторів набував вказівки і таким чином був таким же швидким, як і звичайні числа.

Для людей, що застрягли з діалектами до 11 С ++

Якби я застряг у компіляторі, який не підтримує C ++ 11, я би перейшов із загортанням типу int у клас, який потім дозволяє використовувати лише бітові оператори та типи з цього переліку, щоб встановити його значення:

template<class ENUM,class UNDERLYING=typename std::underlying_type<ENUM>::type>
class SafeEnum
{
public:
    SafeEnum() : mFlags(0) {}
    SafeEnum( ENUM singleFlag ) : mFlags(singleFlag) {}
    SafeEnum( const SafeEnum& original ) : mFlags(original.mFlags) {}

    SafeEnum&   operator |=( ENUM addValue )    { mFlags |= addValue; return *this; }
    SafeEnum    operator |( ENUM addValue )     { SafeEnum  result(*this); result |= addValue; return result; }
    SafeEnum&   operator &=( ENUM maskValue )   { mFlags &= maskValue; return *this; }
    SafeEnum    operator &( ENUM maskValue )    { SafeEnum  result(*this); result &= maskValue; return result; }
    SafeEnum    operator ~()    { SafeEnum  result(*this); result.mFlags = ~result.mFlags; return result; }
    explicit operator bool()                    { return mFlags != 0; }

protected:
    UNDERLYING  mFlags;
};

Ви можете визначити це майже як звичайний enum + typedef:

enum TFlags_
{
    EFlagsNone  = 0,
    EFlagOne    = (1 << 0),
    EFlagTwo    = (1 << 1),
    EFlagThree  = (1 << 2),
    EFlagFour   = (1 << 3)
};

typedef SafeEnum<enum TFlags_>  TFlags;

І використання також схоже:

TFlags      myFlags;

myFlags |= EFlagTwo;
myFlags |= EFlagThree;

if( myFlags & EFlagTwo )
    std::cout << "flag 2 is set" << std::endl;
if( (myFlags & EFlagFour) == EFlagsNone )
    std::cout << "flag 4 is not set" << std::endl;

І ви також можете змінити базовий тип для бінарних стабільних перерахунків (наприклад, C ++ 11's enum foo : type), використовуючи другий параметр шаблону, тобто typedef SafeEnum<enum TFlags_,uint8_t> TFlags;.

Я позначив operator boolпереосмислення explicitключовим словом C ++ 11, щоб запобігти його конверсії int, оскільки вони можуть спричинити згортання наборів прапорів у 0 або 1 при їх написанні. Якщо ви не можете використовувати C ++ 11, залиште це перевантаження і перепишіть перше умовне в прикладі використання як (myFlags & EFlagTwo) == EFlagTwo.


Як зауваження, я б рекомендував, щоб оператор прикладу, визначений на початку використання std::underlying_typeзамість жорсткого кодування конкретного типу, або щоб базовий тип був наданий і використовувався як псевдонім типу замість безпосередньо. Таким чином, зміни базового типу поширюватимуться автоматично, замість того, щоб робити їх вручну.
Час Джастіна -

17

Найпростіший спосіб зробити це , як показано тут , з використанням стандартної бібліотеки класів BitSet .

Щоб імітувати функцію C # безпечним для типу способом, вам доведеться написати обгортку шаблону навколо бітового набору, замінивши аргументи int на enum, заданий як параметр типу шаблону. Щось на зразок:

    template <class T, int N>
class FlagSet
{

    bitset<N> bits;

    FlagSet(T enumVal)
    {
        bits.set(enumVal);
    }

    // etc.
};

enum MyFlags
{
    FLAG_ONE,
    FLAG_TWO
};

FlagSet<MyFlags, 2> myFlag;

4
Подивіться на це для більш повного коду: codereview.stackexchange.com/questions/96146/…
Shital Shah

11

На мою думку, жодна з відповідей поки що не є ідеальною. Як ідеал, я б очікував рішення:

  1. Підтримуйте ==, !=, =, &, &=, |, |=і ~оператори в звичайному сенсі (тобто a & b)
  2. Будьте безпечними для типу, тобто не дозволяйте присвоювати ненумеровані значення, такі як літеральні чи цілі типи (за винятком побітових комбінацій перелічених значень), або дозволяти присвоювати змінній перерахунку цілому типу
  3. Дозволити вирази, такі як if (a & b)...
  4. Не потрібні злі макроси, особливості реалізації або інші хаки

Більшість рішень досі підпадають на точки 2 або 3. На мою думку, WebDancer - це завершення, але воно не вдається в пункті 3, і його потрібно повторювати для кожного перерахунку.

Моє запропоноване рішення - це узагальнена версія WebDancer, яка також стосується точки 3:

#include <cstdint>
#include <type_traits>

template<typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
class auto_bool
{
    T val_;
public:
    constexpr auto_bool(T val) : val_(val) {}
    constexpr operator T() const { return val_; }
    constexpr explicit operator bool() const
    {
        return static_cast<std::underlying_type_t<T>>(val_) != 0;
    }
};

template <typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
constexpr auto_bool<T> operator&(T lhs, T rhs)
{
    return static_cast<T>(
        static_cast<typename std::underlying_type<T>::type>(lhs) &
        static_cast<typename std::underlying_type<T>::type>(rhs));
}

template <typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
constexpr T operator|(T lhs, T rhs)
{
    return static_cast<T>(
        static_cast<typename std::underlying_type<T>::type>(lhs) |
        static_cast<typename std::underlying_type<T>::type>(rhs));
}

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

enum class PlantFlags : uint8_t
{
    HasLeaves = 1,
    HasFlowers = 2,
    HasFruit = 4,
    HasThorns = 8
};

int main()
{
    AnimalFlags seahawk = AnimalFlags::CanFly;        // Compiles, as expected
    AnimalFlags lion = AnimalFlags::HasClaws;         // Compiles, as expected
    PlantFlags rose = PlantFlags::HasFlowers;         // Compiles, as expected
//  rose = 1;                                         // Won't compile, as expected
    if (seahawk != lion) {}                           // Compiles, as expected
//  if (seahawk == rose) {}                           // Won't compile, as expected
//  seahawk = PlantFlags::HasThorns;                  // Won't compile, as expected
    seahawk = seahawk | AnimalFlags::EatsFish;        // Compiles, as expected
    lion = AnimalFlags::HasClaws |                    // Compiles, as expected
           AnimalFlags::Endangered;
//  int eagle = AnimalFlags::CanFly |                 // Won't compile, as expected
//              AnimalFlags::HasClaws;
//  int has_claws = seahawk & AnimalFlags::CanFly;    // Won't compile, as expected
    if (seahawk & AnimalFlags::CanFly) {}             // Compiles, as expected
    seahawk = seahawk & AnimalFlags::CanFly;          // Compiles, as expected

    return 0;
}

Це створює перевантаження необхідних операторів, але використовує SFINAE, щоб обмежити їх переліченими типами. Зауважте, що в інтересах стислості я не визначив усіх операторів, але єдиним, що відрізняється, є &. В даний час оператори є глобальними (тобто застосовуються до всіх перелічених типів), але це може бути зменшено або шляхом розміщення перевантажень у просторі імен (що я роблю), або додаванням додаткових умов SFINAE (можливо, використовуючи конкретні базові типи або спеціально створені псевдоніми типів ). Ця underlying_type_tфункція є C ++ 14, але вона, здається, добре підтримується і легко емулюється для C ++ 11 простоюtemplate<typename T> using underlying_type_t = underlying_type<T>::type;


Незважаючи на те, що запропоноване рішення чудово працює, воно також вводить цю схему для переліків, які не мають розглядатися як прапори. Це, мабуть, причина використання в Microsoft (злих) макросів типу DEFINE_ENUM_FLAG_OPERATORS.
WebDancer

@WebDancer, ви, звичайно, правильні, але тоді я це вже сказав у своїй відповіді. Я також запропонував два способи вирішення проблеми - розміщення її в просторі імен або використання більш обмежувальної умови SFINAE.
Тревор

Моя думка, якщо ви не зробите дійсно вузький простір імен (наприклад, простір імен AllMyFlagEnums) або не маєте умови SFINAE, яке якимось чином вибирає лише декілька точних перерахунків, код порушується в моїй свідомості. Замість того, щоб ризикувати цим, я копіюю та вставляю "текстовий шаблон", де я просто замінюю ім'ям перепису, а іноді і "злими" макросами. Я б хотів, щоб був кращий шлях.
WebDancer

По-перше, це спричинить проблему лише в тому випадку, коли в іншому місці вашого коду вам потрібно виконати одну з речей, яку планується зупинити, наприклад, призначити буква, ціле число або елемент з іншого перерахунку. Інакше модифікований перерахунок поводиться як звичайний перерахунок, наприклад, елементи не обов'язково повинні бути двома силами, а операції з присвоєння, порівняння та розрядів працюють як нормально. Якщо ви дійсно повинні призначити літерали або змішати перерахунки, ви все одно можете чітко подавати, з додатковою перевагою, що ваш намір буде яснішим. Отже, швидше за все, не потрібно буде зменшувати сферу застосування.
Тревор

По-друге, якщо навіть якщо вам потрібно зменшити область, простір імен, можливо, не буде вузьким, хоча це буде залежати від того, що ви робите. Якщо ви працюєте над бібліотекою, то, можливо, у вас вже є ваш код, який залежить від перерахунків у просторі імен, тоді код перерахунку просто перебуває в тому ж просторі імен. Якщо вам потрібна поведінка enum для класу (можливо, ви хочете використовувати enums як аргументи методу або членські змінні класу), тоді для цього ж ефекту введіть код enum у класі. Підсумок - вам не потрібно обертати простір імен навколо лише переліків - хоча ви могли.
Тревор

8

Стандарт C ++ прямо говорить про це, див. Розділ "17.5.2.1.3 Бітмаски типів":

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3485.pdf

Враховуючи цей "шаблон", ви отримуєте:

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

constexpr AnimalFlags operator|(AnimalFlags X, AnimalFlags Y) {
    return static_cast<AnimalFlags>(
        static_cast<unsigned int>(X) | static_cast<unsigned int>(Y));
}

AnimalFlags& operator|=(AnimalFlags& X, AnimalFlags Y) {
    X = X | Y; return X;
}

І подібне для інших операторів. Також зверніть увагу на "constexpr", він потрібен, якщо ви хочете, щоб компілятор міг виконати час збирання операторів.

Якщо ви використовуєте C ++ / CLI і хочете призначити перерахування членів класів ref, вам потрібно використовувати посилання відстеження:

AnimalFlags% operator|=(AnimalFlags% X, AnimalFlags Y) {
    X = X | Y; return X;
}

ПРИМІТКА: Цей зразок не є повним, див. Розділ "17.5.2.1.3 Типи біткої маски" для повного набору операторів.


6

Я виявив, що задаю те саме питання, і придумав загальне рішення на основі C ++ 11, подібне до сору:

template <typename TENUM>
class FlagSet {

private:
    using TUNDER = typename std::underlying_type<TENUM>::type;
    std::bitset<std::numeric_limits<TUNDER>::max()> m_flags;

public:
    FlagSet() = default;

    template <typename... ARGS>
    FlagSet(TENUM f, ARGS... args) : FlagSet(args...)
    {   
        set(f);
    }   
    FlagSet& set(TENUM f)
    {   
        m_flags.set(static_cast<TUNDER>(f));
        return *this;
    }   
    bool test(TENUM f)
    {   
        return m_flags.test(static_cast<TUNDER>(f));
    }   
    FlagSet& operator|=(TENUM f)
    {   
        return set(f);
    }   
};

Інтерфейс можна вдосконалити за смаком. Тоді його можна використовувати так:

FlagSet<Flags> flags{Flags::FLAG_A, Flags::FLAG_C};
flags |= Flags::FLAG_D;

2
Подивіться на це для кращого та повнішого коду: codereview.stackexchange.com/questions/96146/…
Shital Shah

5
За винятком мого використання numeric_limits, код майже той самий. Я здогадуюсь, що це звичайний спосіб мати безпечний для типу клас перерахунків. Я заперечую, що використовувати numeric_limits краще, ніж ставити SENTINEL в кінці кожного перерахунку.
Омаїр

1
Це величезний біт!
Гонки легкості на орбіті


5

Якщо ваш компілятор ще не підтримує сильно набрані перерахунки, ви можете переглянути наступну статтю з джерела c ++:

З реферату:

У цій статті представлено рішення проблеми обмеження бітових операцій, щоб
дозволити лише безпечні та законні, а також перетворити всі недійсні бітові маніпуляції в помилки компіляції. Найкраще, що синтаксис бітових операцій залишається незмінним, і код, що працює з бітами, не потрібно змінювати, за винятком можливого виправлення помилок, які досі залишалися невиявленими.


5

Я використовую такий макрос:

#define ENUM_FLAG_OPERATORS(T)                                                                                                                                            \
    inline T operator~ (T a) { return static_cast<T>( ~static_cast<std::underlying_type<T>::type>(a) ); }                                                                       \
    inline T operator| (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) | static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T operator& (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) & static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T operator^ (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) ^ static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T& operator|= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) |= static_cast<std::underlying_type<T>::type>(b) ); }   \
    inline T& operator&= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) &= static_cast<std::underlying_type<T>::type>(b) ); }   \
    inline T& operator^= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) ^= static_cast<std::underlying_type<T>::type>(b) ); }

Він подібний до вищезазначених, але має ряд удосконалень:

  • Це безпечний тип (він не передбачає, що базовий тип є int)
  • Він не вимагає вручну вказувати базовий тип (на відміну від відповіді @LunarEclipse)

Тут потрібно включати type_traits:

#include <type_traits>

4

Я хотів би детальніше розглянути відповідь Uliwitness , виправити його код для C ++ 98 та використовувати ідіому Safe Bool для відсутності 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;
};

3

Ось варіант для біткових масок, якщо ви фактично не використовуєте окремі значення перерахунків (наприклад, вам не потрібно їх вимикати) ... і якщо ви не переживаєте за збереження бінарної сумісності, тобто: байдуже, де живуть твої шматочки ... якими ти, мабуть, є. Також вам краще не надто перейматися масштабуванням та контролем доступу. Гммм, енюми мають деякі приємні властивості для бітових полів ... цікаво, чи хтось коли-небудь це пробував :)

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 :(";
    }
}

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

Незважаючи на те, що все чудово ... ми маємо певний контекст щодо значення прапорів зараз, оскільки його в об'єднанні з бітфілдом у жахливому реальному світі, де ваша програма може відповідати за більш ніж одне окреме завдання, яке ви могли б зробити як і раніше випадково (досить легко) разом розбийте два прапори різних союзів (скажімо, AnimalProperties та ObjectProperties, оскільки вони обидва ints), змішавши всі ваші біти, що є жахливою помилкою відстежити ... і як я знаю багато людей на цій посаді не працюють з растровими масками дуже часто, оскільки створити їх легко, а підтримувати їх важко.

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) визначити біт макет під час компіляції (так що ви можете відстежувати його і принаймні виправляти зміни в межах версії / платформи тощо) Зміщення в структурі з бітовими полями

Під час виконання ви можете грати з трюками під час встановлення полів та XORing прапорів, щоб побачити, які біти змінилися, для мене звучить досить шалено, хоча вірші, які мають 100% послідовне, незалежне від платформи, і повністю детерміноване рішення, тобто: ENUM.

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

Якщо ви хочете, щоб ваш код був змодельований на розмовній мові, вам найкраще буде писати в Objective-C, який, до речі, також сильно використовує перерахунки для бітфілів.


3

Тільки синтаксичний цукор. Без додаткових метаданих.

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;
}

Оператори прапорів інтегрального типу просто працюють.


ІМХО це найкраща відповідь. Чистий і простий, простий синтаксис клієнта. Я просто використовую "const int", а не "constexpr uint8_t", але концепція та ж.
yoyo

(вибачте, "constexpr int")
yoyo

3

Наразі не існує мовної підтримки для enum прапорів, Meta-класи можуть по суті додавати цю функцію, якщо вона коли-небудь буде частиною стандарту c ++.

Моє рішення полягало б у створенні функцій шаблону, створених лише для переліку, що додають підтримку безпечних для побітових операцій класу enum, використовуючи його базовий тип:

Файл: 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;
}

3

@Xaqq надав дійсно хороший спосіб тіпобезопасний використовувати перерахувань прапори тут по flag_setкласу.

Я опублікував код у GitHub , використання наступне:

#include "flag_set.hpp"

enum class AnimalFlags : uint8_t {
    HAS_CLAWS,
    CAN_FLY,
    EATS_FISH,
    ENDANGERED,
    _
};

int main()
{
    flag_set<AnimalFlags> seahawkFlags(AnimalFlags::HAS_CLAWS
                                       | AnimalFlags::EATS_FISH
                                       | AnimalFlags::ENDANGERED);

    if (seahawkFlags & AnimalFlags::ENDANGERED)
        cout << "Seahawk is endangered";
}

2

Ви плутаєте об'єкти та колекції об’єктів. Зокрема, ви плутаєте бінарні прапори з наборами бінарних прапорів. Правильне рішення виглядатиме так:

// 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; }

};

2

Ось моє рішення, не потребуючи жодної купу перевантажень чи кастингу:

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.


До речі, я скоріше не рекомендую перераховувати прапори. Проста причина: комбінації прапорів знову не є елементами перерахунку. Тож це здається зовсім непридатним. Крім того, я б використовував using Flags = unsigned longусередині простору імен або структуру, що містить самі значення прапорців /*static*/ const Flags XY = 0x01і так далі.
1616

1

Як вище (Кай) або зробіть наступне. Дійсно перерахунки - це "Перерахування", що ви хочете зробити, це мати набір, тому вам дійсно слід використовувати 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);
}

1

Можливо, як 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.

Чи можете ви пояснити, чому ваша відповідь найкраще підходить? Є кілька інших відповідей, які відповіли на це питання, тому, будь ласка, включіть трохи інформації, щоб відрізняти вашу.
треворп
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.