簡體   English   中英

如何在 C++ 中使用枚舉作為標志?

[英]How to use enums as flags in C++?

通過[Flags]屬性將enum視為標志在 C# 中效果很好,但在 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

“正確”的方法是為枚舉定義位運算符,如下所示:

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 范圍,則根據需要進行修改。

注意(也有點離題):另一種制作獨特標志的方法可以使用位移位來完成。 我,我自己,發現這更容易閱讀。

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

它最多可以保存一個 int 值,因此在大多數情況下,32 個標志清楚地反映在移位量中。

對於像我這樣的懶人,這里是復制和粘貼的模板化解決方案:

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

請注意,如果您在 Windows 環境中工作,則在DEFINE_ENUM_FLAG_OPERATORS定義了一個DEFINE_ENUM_FLAG_OPERATORS宏,可以為您完成這項工作。 所以在這種情況下,你可以這樣做:

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

seahawk.flags = CanFly | EatsFish | Endangered;

seahawk.flags 變量是什么類型?

在標准 C++ 中,枚舉不是類型安全的。 它們實際上是整數。

AnimalFlags 不應該是你的變量的類型。 你的變量應該是 int 並且錯誤會消失。

不需要像其他人建議的那樣放置十六進制值。 沒有什么不同的。

默認情況下,枚舉值是 int 類型。 所以你當然可以按位或組合它們並將它們放在一起並將結果存儲在一個整數中。

枚舉類型是 int 的受限子集,其值是其枚舉值之一。 因此,當您在該范圍之外創建一些新值時,您無法在不強制轉換為枚舉類型的變量的情況下對其進行分配。

如果您願意,您也可以更改枚舉值類型,但這個問題沒有意義。

編輯:海報說他們關心類型安全,他們不想要一個不應該存在於 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

我發現eidolon目前接受的答案太危險了。 編譯器的優化器可能會對枚舉中的可能值做出假設,並且您可能會得到無效值的垃圾。 通常沒有人想在標志枚舉中定義所有可能的排列。

正如下面的 Brian R. Bondy 所說,如果您使用的是 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++ 方言的人

如果我堅持使用不支持 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 的enum foo : type ),即typedef SafeEnum<enum TFlags_,uint8_t> TFlags; .

我用 C++11 的explicit關鍵字標記了operator bool override 以防止它導致 int 轉換,因為這可能導致標志集在寫出時最終折疊為 0 或 1。 如果您不能使用 C++11,請忽略該重載並將示例用法中的第一個條件重寫為(myFlags & EFlagTwo) == EFlagTwo

最簡單的方法來做到這一點,如圖在這里,使用標准庫類的bitset

要以類型安全的方式模擬 C# 功能,您必須圍繞 bitset 編寫模板包裝器,將 int 參數替換為作為模板類型參數提供的枚舉。 就像是:

    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;

在我看來,到目前為止,沒有一個答案是理想的。 為了理想,我希望解決方案:

  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 特性,但它似乎得到了很好的支持,並且很容易用一個簡單的template<typename T> using underlying_type_t = underlying_type<T>::type;來模擬 C++11, template<typename T> using underlying_type_t = underlying_type<T>::type;

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 位掩碼類型”部分以獲取完整的運算符集。

我使用以下宏:

#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>

我發現自己在問同樣的問題,並提出了一個基於 C++11 的通用解決方案,類似於 soru:

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;

如果您的編譯器還不支持強類型枚舉,您可以從 c++ 源代碼中查看以下文章

從摘要:

本文提出了將位操作約束為的問題的解決方案
只允許安全和合法的操作,並將所有無效的位操作轉換為編譯時錯誤。 最重要的是,位操作的語法保持不變,並且不需要修改處理位的代碼,除非可能修復尚未檢測到的錯誤。

我想詳細說明Uliwitness answer ,修復他的 C++98 代碼並使用Safe Bool idiom ,因為在 C++11 以下的 C++ 版本中缺少std::underlying_type<>模板和explicit關鍵字。

我還修改了它,以便枚舉值可以是連續的,而無需任何顯式賦值,因此您可以擁有

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

只有語法糖。 沒有額外的元數據。

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

整數類型的標志運算符才有效。

目前沒有對枚舉標志的語言支持,如果元類成為 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;
}

如果您實際上沒有使用單個枚舉值(例如,您不需要關閉它們),那么這是位掩碼的一個選項……並且如果您不擔心維護二進制兼容性,即:不在乎你的位住在哪里......你可能在哪里。 此外,您最好不要太關注范圍和訪問控制。 嗯,枚舉對位域有一些很好的屬性......想知道是否有人嘗試過:)

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

我們可以看到生活是美好的,我們有離散的值,我們有一個很好的 & 和 | 整數。 我們的心滿意足,它仍然具有其位含義的上下文。 一切都是一致和可預測的......對我來說......只要我在Win10 x64上繼續使用帶有更新3的Microsoft VC++編譯器並且不要觸摸我的編譯器標志:)

即使一切都很好……我們現在對標志的含義有了一些了解,因為它與可怕的現實世界中的位域結合在一起,在那里您的程序可能負責的不僅僅是一個單獨的任務,您可以仍然不小心(很容易)將不同聯合的兩個標志字段粉碎在一起(例如,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 :(
}

因此,您將聯合聲明設為私有以防止直接訪問“標志”,並且必須添加 getter/setter 和運算符重載,然后為所有這些創建一個宏,並且您基本上回到了您嘗試的起點使用 Enum 執行此操作。

不幸的是,如果您希望您的代碼是可移植的,我認為沒有任何方法可以 A)保證位布局或 B)在編譯時確定位布局(因此您可以跟蹤它並至少糾正跨版本/平台等) 在具有位字段的結構中的偏移量

在運行時,您可以通過設置字段和對標志進行異或來查看哪些位確實發生了變化,這對我來說聽起來很糟糕,盡管具有 100% 一致、獨立於平台且完全確定性的解決方案的詩句,即:ENUM。

TL;DR:不要聽那些仇恨者的意見。 C++ 不是英語。 僅僅因為從 C 繼承的縮寫關鍵字的字面定義可能不適合您的用法,並不意味着當關鍵字的 CC++ 定義絕對包含您的用例時,您不應該使用它。 您還可以使用結構來為結構以外的事物建模,以及為學校和社會等級以外的事物建模。 您可以對接地的值使用浮點數。 您可以將 char 用於既不是未燃燒的也不是小說、戲劇或電影中的人的變量。 任何在語言規范之前去字典確定關鍵字含義的程序員都是......好吧,我會保持沉默。

如果你確實希望你的代碼以口語為模型,你最好用 Objective-C 編寫,順便說一句,它也大量使用枚舉作為位域。

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

您混淆了對象和對象集合。 具體來說,您將二進制標志與二進制標志集混淆了。 正確的解決方案如下所示:

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

};

這是我的解決方案,不需要任何重載或強制轉換:

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

另一個宏解決方案,但與現有答案不同,它不使用reinterpret_cast (或 C-cast)在Enum&Int& ,這在標准 C++ 中是禁止的(參見這篇文章)。

#define MAKE_FLAGS_ENUM(TEnum, TUnder)                                                                                             \
TEnum  operator~  ( TEnum  a          ) { return static_cast<TEnum> (~static_cast<TUnder> (a)                           ); }  \
TEnum  operator|  ( TEnum  a, TEnum b ) { return static_cast<TEnum> ( static_cast<TUnder> (a) |  static_cast<TUnder>(b) ); }  \
TEnum  operator&  ( TEnum  a, TEnum b ) { return static_cast<TEnum> ( static_cast<TUnder> (a) &  static_cast<TUnder>(b) ); }  \
TEnum  operator^  ( TEnum  a, TEnum b ) { return static_cast<TEnum> ( static_cast<TUnder> (a) ^  static_cast<TUnder>(b) ); }  \
TEnum& operator|= ( TEnum& a, TEnum b ) { a = static_cast<TEnum>(static_cast<TUnder>(a) | static_cast<TUnder>(b) ); return a; }  \
TEnum& operator&= ( TEnum& a, TEnum b ) { a = static_cast<TEnum>(static_cast<TUnder>(a) & static_cast<TUnder>(b) ); return a; }  \
TEnum& operator^= ( TEnum& a, TEnum b ) { a = static_cast<TEnum>(static_cast<TUnder>(a) ^ static_cast<TUnder>(b) ); return a; }

丟失reinterpret_cast意味着我們不能再依賴x |= y語法,而是通過將它們擴展為它們的x = x | y x = x | y形式我們不再需要它。

注意:您可以使用std::underlying_type來獲取TUnder ,為簡潔起見,我沒有將其包括在內。

如上(Kai)或執行以下操作。 真正的枚舉是“枚舉”,你想做的是有一個集合,因此你應該真正使用 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);
}

也許像Objective-C的NS_OPTIONS。

#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.

基於此線程中的其他一些答案,可復制粘貼的“邪惡”宏:

#include <type_traits>

/*
 * Macro to allow enum values to be combined and evaluated as flags.
 *  * Based on:
 *  - DEFINE_ENUM_FLAG_OPERATORS from <winnt.h>
 *  - https://stackoverflow.com/a/63031334/1624459
 */
#define MAKE_ENUM_FLAGS(TEnum)                                                      \
    inline TEnum operator~(TEnum a) {                                               \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        return static_cast<TEnum>(~static_cast<TUnder>(a));                         \
    }                                                                               \
    inline TEnum operator|(TEnum a, TEnum b) {                                      \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        return static_cast<TEnum>(static_cast<TUnder>(a) | static_cast<TUnder>(b)); \
    }                                                                               \
    inline TEnum operator&(TEnum a, TEnum b) {                                      \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        return static_cast<TEnum>(static_cast<TUnder>(a) & static_cast<TUnder>(b)); \
    }                                                                               \
    inline TEnum operator^(TEnum a, TEnum b) {                                      \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        return static_cast<TEnum>(static_cast<TUnder>(a) ^ static_cast<TUnder>(b)); \
    }                                                                               \
    inline TEnum& operator|=(TEnum& a, TEnum b) {                                   \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        a = static_cast<TEnum>(static_cast<TUnder>(a) | static_cast<TUnder>(b));    \
        return a;                                                                   \
    }                                                                               \
    inline TEnum& operator&=(TEnum& a, TEnum b) {                                   \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        a = static_cast<TEnum>(static_cast<TUnder>(a) & static_cast<TUnder>(b));    \
        return a;                                                                   \
    }                                                                               \
    inline TEnum& operator^=(TEnum& a, TEnum b) {                                   \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        a = static_cast<TEnum>(static_cast<TUnder>(a) ^ static_cast<TUnder>(b));    \
        return a;                                                                   \
    }

用法

enum class Passability : std::uint8_t {
    Clear      = 0,
    GroundUnit = 1 << 1,
    FlyingUnit = 1 << 2,
    Building   = 1 << 3,
    Tree       = 1 << 4,
    Mountain   = 1 << 5,
    Blocked    = 1 << 6,
    Water      = 1 << 7,
    Coastline  = 1 << 8
};

MAKE_ENUM_FLAGS(Passability)

好處

  • 僅在顯式使用時適用於選定的枚舉。
  • 不使用非法的reinterpret_cast
  • 無需指定底層類型。

筆記

  • 如果使用 C++ <14, std::underlying_type_t<TEnum>替換為std::underlying_type<TEnum>::type

C++20 類型安全枚舉運算符

長話短說

template<typename T>
requires std::is_enum_v<T> and
         requires (std::underlying_type_t<T> x) {
             { x | x } -> std::same_as<std::underlying_type_t<T>>;
             T(x);
         }
T operator|(T left, T right)
{
    using U = std::underlying_type_t<T>;
    return T( U(left) | U(right) );
}

template<typename T>
requires std::is_enum_v<T> and
         requires (std::underlying_type_t<T> x) {
             { x | x } -> std::same_as<std::underlying_type_t<T>>;
             T(x);
         }
T operator&(T left, T right)
{
    using U = std::underlying_type_t<T>;
    return T( U(left) & U(right) );
}

template<typename T>
requires std::is_enum_v<T> and requires (T x) { { x | x } -> std::same_as<T>; }
T & operator|=(T &left, T right)
{
    return left = left | right;
}

template<typename T>
requires std::is_enum_v<T> and requires (T x) { { x & x } -> std::same_as<T>; }
T & operator&=(T &left, T right)
{
    return left = left & right;
}

基本原理

使用類型特征std::is_enum我們可以測試某個類型T是否是枚舉類型。 這包括未限定范圍和限定范圍的枚舉(即enumenum class )。 通過類型特征std::underlying_type我們可以獲得枚舉的底層類型 有了 C++20 的概念和約束,就可以很容易地為按位運算提供重載。

作用域與非作用域

如果僅應為范圍內或非范圍內的枚舉重載操作,則std::is_scoped_enum可用於相應地擴展模板約束。

C++23

使用 C++23,我們可以使用std::to_underlying更輕松地將枚舉值轉換為其基礎類型。

移動語義 & 完美轉發

如果您遇到奇怪的情況,即您的基礎類型對於復制與移動具有不同的語義,或者它不提供復制 c'tor,那么您應該使用std::forward完美轉發操作數。

您可以使用結構如下:

struct UiFlags2 {
    static const int
    FULLSCREEN = 0x00000004,               //api 16
    HIDE_NAVIGATION = 0x00000002,          //api 14
    LAYOUT_HIDE_NAVIGATION = 0x00000200,   //api 16
    LAYOUT_FULLSCREEN = 0x00000400,        //api 16
    LAYOUT_STABLE = 0x00000100,            //api 16
    IMMERSIVE_STICKY = 0x00001000;         //api 19
};

並使用如下:

int flags = UiFlags2::FULLSCREEN | UiFlags2::HIDE_NAVIGATION;

所以你不需要int轉換,它可以直接使用。
它也是像enum class一樣的范圍分隔

這是一個懶惰的 C++11 解決方案,它不會改變枚舉的默認行為。 它也適用於enum structenum class ,並且是constexpr

#include <type_traits>

template<class T = void> struct enum_traits {};

template<> struct enum_traits<void> {
    struct _allow_bitops {
        static constexpr bool allow_bitops = true;
    };
    using allow_bitops = _allow_bitops;

    template<class T, class R = T>
    using t = typename std::enable_if<std::is_enum<T>::value and
        enum_traits<T>::allow_bitops, R>::type;

    template<class T>
    using u = typename std::underlying_type<T>::type;
};

template<class T>
constexpr enum_traits<>::t<T> operator~(T a) {
    return static_cast<T>(~static_cast<enum_traits<>::u<T>>(a));
}
template<class T>
constexpr enum_traits<>::t<T> operator|(T a, T b) {
    return static_cast<T>(
        static_cast<enum_traits<>::u<T>>(a) |
        static_cast<enum_traits<>::u<T>>(b));
}
template<class T>
constexpr enum_traits<>::t<T> operator&(T a, T b) {
    return static_cast<T>(
        static_cast<enum_traits<>::u<T>>(a) &
        static_cast<enum_traits<>::u<T>>(b));
}
template<class T>
constexpr enum_traits<>::t<T> operator^(T a, T b) {
    return static_cast<T>(
        static_cast<enum_traits<>::u<T>>(a) ^
        static_cast<enum_traits<>::u<T>>(b));
}
template<class T>
constexpr enum_traits<>::t<T, T&> operator|=(T& a, T b) {
    a = a | b;
    return a;
}
template<class T>
constexpr enum_traits<>::t<T, T&> operator&=(T& a, T b) {
    a = a & b;
    return a;
}
template<class T>
constexpr enum_traits<>::t<T, T&> operator^=(T& a, T b) {
    a = a ^ b;
    return a;
}

要為枚舉啟用按位運算符:

enum class my_enum {
    Flag1 = 1 << 0,
    Flag2 = 1 << 1,
    Flag3 = 1 << 2,
    // ...
};

// The magic happens here
template<> struct enum_traits<my_enum> :
    enum_traits<>::allow_bitops {};

constexpr my_enum foo = my_enum::Flag1 | my_enum::Flag2 | my_enum::Flag3;

我更喜歡使用magic_enum ,因為它有助於自動將字符串轉換為枚舉,反之亦然。 它是一個以 C++17 標准編寫的僅標頭庫。

magic_enum已經有枚舉按位運算符的模板函數。 請參閱文檔

用法:

#include <magic_enum.hpp>

enum Flag { ... };

Flag flag{};
Flag value{};

using namespace magic_enum::bitwise_operators;
flag |= value;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM