C ++에서 열거 형을 플래그로 사용하는 방법은 무엇입니까?


187

enums를 플래그로 취급하면 C #에서 [Flags]속성을 통해 잘 작동 하지만 C ++에서 이것을 수행하는 가장 좋은 방법은 무엇입니까?

예를 들어 다음과 같이 작성하고 싶습니다.

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

seahawk.flags = CanFly | EatsFish | Endangered;

그러나 int/ enum변환 과 관련된 컴파일러 오류가 발생합니다 . 무딘 주물보다 이것을 표현하는 더 좋은 방법이 있습니까? 바람직하게는 부스트 또는 Qt와 같은 타사 라이브러리의 구성에 의존하고 싶지 않습니다.

편집 : 마찬가지로 내가 선언 컴파일러 오류를 방지 할 수 있습니다 답변에 표시된 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, 아니요 C ++ (2015)에서도 작동하지 않습니다. C #을 의미 했습니까?
Ajay

5
@rivanov, [Flags] 속성은 C ++ CLI의 .Net Framework에서만 작동하며 네이티브 C ++는 이러한 속성을 지원하지 않습니다.
Zoltan Tirinda

답변:


250

"올바른"방법은 다음과 같이 열거 형에 대한 비트 연산자를 정의하는 것입니다.

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 유형으로 캐스트 된 것이 유효합니까?
Ingo Schalk-Schupp

8
이건 말도 안돼 어느 멤버가 AnimalFlags표현식으로 표시 HasClaws | CanFly됩니까? 입니다 하지 무엇 enum의가 있습니다. 정수와 상수를 사용하십시오.
궤도에서 가벼움 경주

26
@LightnessRacesinOrbit : 맞지 않습니다. 열거 형의 도메인은 기본 유형의 도메인입니다. 특정 도메인에만 이름이 지정되었습니다. 그리고 귀하의 질문에 대답하기 위해 : " (HasClaws | CanFly)" 회원 .
Xeo

5
@MarcusJ : 값을 2의 거듭 제곱으로 제한하면 열거 형을 비트 플래그로 사용할 수 있습니다. 따라서 3을 얻으면 HasClaws(= 1)과 CanFly(= 2)를 모두 알 수 있습니다 . 대신 당신은 그냥 직선을 통해 4까지의 값 1을 할당하고 3을 얻을 경우, 하나의 수 있습니다 EatsFish, 또는 다시 조합 HasClawsCanFly. 열거가 배타적 상태만을 나타내면 연속 값은 괜찮지 만 플래그 조합은 값을 비트 배타적이어야합니다.
Christian Severin

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)를 삭제하고} 뒤에 콜론을 추가 할 수 있습니까? 감사합니다
Katu

4
16 진법에 대한 언급이 없습니까? 신성 모독!
Pharap

1
@Jamie, 추기경은 항상 1로 시작하며, 말한 사람에 따라 서 수만 0 또는 1로 시작할 수 있습니다.
Michael

2
@ 마이클, 맞습니다! 열거 형에서는 일반적으로 BLAH_NONE에 대해 0을 예약합니다. :-) 그 기억을 찌르는 것에 감사합니다!
Jamie

1
@Katu • 최종 열거에 불필요한 쉼표가 표준에 의해 허용됩니다. 나는 그것을 좋아하지 않지만, Stroustrup이 나에게 무엇을 말할지 이미 알고 있습니다.
Eljay 2019

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

@Rai : 언제나 using처럼 네임 스페이스와 적절한 곳에 배치 할 수 있습니다 rel_ops.
Yakov Galka

1
@ybungalobill,하지만 여전히 enum과 일치하는 사용 범위의 모든 유형에 적용되는 작업과 동일한 문제가 있습니까? 나는 특성이 가장 필요하다고 생각합니다.
Rai

19
이 코드를 사용하지 마십시오. 모든 수업이 실수로 운영 될 수있는 기회를 제공합니다. 또한 코드는 GCC 엄격한 컴파일 shitalshah.com/p/…을 통과하지 않는 이전 스타일 캐스트를 사용 합니다.
Shital Shah

44

Windows 환경에서 작업중인 경우 DEFINE_ENUM_FLAG_OPERATORSwinnt.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이어야하며 오류가 사라집니다.

다른 사람들이 제안한 것처럼 16 진수 값을 넣을 필요는 없습니다. 차이가 없습니다.

열거 형 값은 기본적으로 int 유형입니다. 따라서 비트 단위로 OR하거나 결합하여 결과를 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을 만들고 각 플래그를 그 안에 저장할 수 있습니다. 공간 효율적이지 않지만 형식이 안전하며 비트 플래그 int와 동일한 기능을 제공합니다.

C ++ 0x 참고 : 강력한 형식의 열거 형

C ++ 0x에서는 마침내 타입 안전 열거 형 값을 가질 수 있습니다 ....

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

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

4
열거 형 값은 정수가 아니지만 매우 쉽게 정수로 변환됩니다. 의 유형은 HasClaws | CanFly어떤 정수 타입 만의 유형입니다 HasClawsIS AnimalFlags가 아닌 정수 유형입니다.
Karu

1
아, 그러나 열거 형의 올바른 범위를 개별 플래그 값뿐만 아니라 비트 조합으로 정의하면 어떻게 될까요? 그런 다음 eidolon의 대답은 정확하며 올바른 플래그 열거 형의 조합 만 해당 유형으로 전달 될 수 있다고 유지합니다.
Scott

3
@ 스콧 : C ++ 표준은 열거 형 인스턴스의 유효한 값 범위를 그런 식으로 정의한다는 점에 주목할 가치가 있습니다. "emin이 가장 작은 열거 자이고 emax가 가장 큰 열거의 경우 열거의 값은 bmin ~ bmax 범위의 값이며 다음과 같이 정의됩니다. 보완 또는 사인 진폭 표현. B 최대보다 작은 값 크거나 동일 max(|emin| − K, |emax|)하고 동일한 (1u<<M) - 1여기서 M음이 아닌 정수이다. "
Ben Voigt 2016 년

(나 같은) 열거 형 값을 비트 단위로 조작하고 템플릿과 유형 캐스팅으로 너무보기 흉하지 않은 실용적인 무언가를 원하는 사람들에게 이것은 좋은 해결책입니다. 변수를 type으로 정의하십시오 int.
Eric Sokolowsky

또한 C ++에서 regular enum는 기본적으로 int기본 유형 (기본 C ++ 11 (IIRC) 또는 기본 유형이 지정되지 않은 경우 C ++ 11 이후)으로 기본 설정 enum class 되지 않습니다 . 대신 기본 형식은 기본적으로 모든 열거자를 나타낼 정도로 충분히 큰 것으로 지정 int해야합니다. 명시 적으로 필요한 것보다 더 큰 유일한 실제 규칙입니다 . 기본적으로, 기본 유형 (의역)로 지정됩니다 "작동 어떤하지만, 그건 아마 int 열거자를이 너무 커서 않는 한 int".
저스틴 타임-복원 모니카

26

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. 

이렇게하면 열거 형의 유형을 지정하여 안정적인 크기와 값 범위를 보장하고을 사용하여 열거 형을 int 등으로 자동 다운 캐스팅 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;

그리고 enum foo : type두 번째 템플릿 매개 변수를 사용하여 이진 안정 열거 형 (C ++ 11과 같은)의 기본 유형을 재정의 할 수도 있습니다 typedef SafeEnum<enum TFlags_,uint8_t> TFlags;.

플래그를 작성할 때 플래그 집합이 0 또는 1로 축소 될 수 있기 때문에 int 변환이 발생하지 않도록 operator boolC ++ 11의 explicit키워드로 재정의를 표시 했습니다. C ++ 11을 사용할 수 없다면, 오버로드를 남겨두고 예제 사용법에서 첫 번째 조건을로 다시 작성하십시오 (myFlags & EFlagTwo) == EFlagTwo.


참고로, std::underlying_type특정 유형을 하드 코딩하는 대신 시작시 정의 된 예제 연산자를 사용 하거나 기본 유형을 직접 제공하는 대신 유형 별명으로 제공하고 사용하는 것이 좋습니다. 이렇게하면 기본 유형에 대한 변경 사항이 수동으로 수행되는 대신 자동으로 전파됩니다.
저스틴 타임-복원 모니카

17

표준 라이브러리 클래스 비트 세트를 사용하여 여기 에 표시된대로 가장 쉬운 방법 입니다.

형식이 안전한 방식으로 C # 기능을 에뮬레이트하려면 비트 집합 주위에 템플릿 래퍼를 작성하여 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;

4
보다 완전한 코드는 다음을 참조하십시오 : codereview.stackexchange.com/questions/96146/…
Shital Shah

11

제 생각에는 지금까지 답변이 이상적입니다. 이상적으로 나는 해결책을 기대할 것입니다 :

  1. 지원 ==, !=, =, &, &=, |, |=~ (즉, 통상적 인 의미에서 사업자 a & b)
  2. 형식이 안전해야합니다. 즉 리터럴 또는 정수 유형과 같은 열거되지 않은 값을 할당 할 수 없으며 (열거 된 값의 비트 조합 제외) 열거 형 변수를 정수 유형에 할당 할 수 없습니다
  3. 다음과 같은 표현 허용 if (a & b)...
  4. 사악한 매크로, 구현 특정 기능 또는 기타 해킹이 필요하지 않음

지금까지 대부분의 솔루션은 포인트 2 또는 3에 해당합니다.

내가 제안한 솔루션은 포인트 3을 다루는 WebDancer의 일반화 된 버전입니다.

#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_tC ++ 14 기능이지만 잘 지원 될 것으로 보인다 간단한와 C ++ 11 에뮬레이션 용이template<typename T> using underlying_type_t = underlying_type<T>::type;


제안 된 솔루션은 훌륭하게 작동하지만 플래그로 취급되지 않는 열거 형에 대해서도이 패턴을 소개합니다. 아마도 Microsoft의 DEFINE_ENUM_FLAG_OPERATORS와 같은 (사악한) 매크로를 사용하는 이유 일 것입니다.
WebDancer

@ WebDancer, 당신은 물론 정확하지만, 나는 이미 내 대답에서 그것을 말했습니다. 또한 문제를 해결하는 두 가지 방법, 즉 네임 스페이스에 넣거나보다 제한적인 SFINAE 조건을 사용하는 방법도 제안했습니다.
Trevor

내 요점은 당신이 정말로 좁은 네임 스페이스 (예 : 네임 스페이스 AllMyFlagEnums)를 만들거나 SFINAE 조건을 가지고 있지 않으면 어떤 식 으로든 몇 가지 정확한 열거 형을 선택하지 않으면 코드가 마음에 들지 않는 것입니다. 이 위험을 감수하기보다는 열거 형 이름과 때로는 "악한"매크로를 바꾸는 "텍스트 템플릿"을 복사하여 붙여 넣습니다. 더 좋은 방법이 있었으면 좋겠다.
WebDancer

첫째, 코드의 다른 곳에서 중지하려고하는 것 중 하나를 수행 해야하는 경우에만 문제가 발생합니다 (예 : 다른 열거 형의 리터럴, 정수 또는 요소 할당). 그렇지 않으면 수정 된 열거 형은 일반 열거 형처럼 동작합니다. 예를 들어 요소가 반드시 2의 거듭 제곱 일 필요는 없으며 할당, 비교 및 ​​비트 단위 연산이 정상적으로 작동합니다. 리터럴이나 믹스 열거 형을 실제로 할당 해야하는 경우에도 명시 적으로 캐스팅 할 수 있으며 의도가 명확해질 수 있다는 이점이 있습니다. 따라서 범위를 줄일 필요가 없을 가능성이 있습니다.
Trevor

둘째, 범위를 줄여야하더라도 네임 스페이스를 좁힐 필요는 없습니다. 비록 수행중인 작업에 따라 다릅니다. 라이브러리에서 작업하는 경우 네임 스페이스의 열거 형에 의존하는 코드가 이미있을 수 있습니다. 그런 다음 열거 형 코드는 동일한 네임 스페이스에 있습니다. 클래스에 대해 열거 형 동작이 필요한 경우 (아마도 열거 형을 메소드 인수 또는 클래스의 멤버 변수로 사용하려는 경우) 동일한 효과를 위해 클래스에 열거 형 코드를 넣습니다. 결론은 열거 형 주위에 네임 스페이스를 래핑 할 필요가 없다는 것입니다.
Trevor

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를 사용하고 참조 클래스의 열거 형 멤버에 지정하려면 추적 참조를 대신 사용해야합니다.

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

참고 :이 샘플은 완료되지 않았습니다. 전체 연산자 세트는 "17.5.2.1.3 비트 마스크 유형"섹션을 참조하십시오.


6

나는 똑같은 질문을하고 soru와 비슷한 일반적인 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 샤

5
numeric_limits를 사용하는 것을 제외하고 코드는 거의 동일합니다. 형식 안전 열거 형 클래스를 사용하는 일반적인 방법이라고 생각합니다. numeric_limits를 사용하는 것이 모든 열거의 끝에 SENTINEL을 배치하는 것보다 낫다고 주장합니다.
Omair

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<>템플릿과explicitC ++ 11 이하의 C ++ 버전 키워드가 .

또한 열거 형 값이 명시 적 할당없이 순차적이 될 수 있도록 수정 했으므로

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

우리는 삶이 위대하다는 것을 알 수 있으며, 우리는 이산적인 가치를 지니고 있으며 &에 대한 좋은 정보를 가지고 있습니다. 비트 내용이 무엇인지에 대한 맥락을 가지고 있습니다. Win10 x64에서 업데이트 3이있는 Microsoft의 VC ++ 컴파일러를 계속 사용하고 컴파일러 플래그를 만지지 않는 한 모든 것이 일관되고 예측 가능합니다 ...

모든 것이 훌륭하지만 ... 우리는 플래그의 의미에 대해 약간의 맥락을 가지고 있습니다. 왜냐하면 당신의 프로그램이 당신이 할 수있는 하나의 개별 작업보다 더 많은 책임을 질 수있는 끔찍한 현실 세계의 비트 필드와 결합하기 때문입니다. 여전히 우연히 (쉽게) 쉽게 다른 조합의 두 플래그 필드 (예 : AnimalProperties와 ObjectProperties, 둘 다 int이므로)를 으깨어 모든 비트를 섞습니다. 이것은 추적하는 끔찍한 버그입니다. 이 게시물의 많은 사람들은 비트 마스크를 자주 사용하지 않습니다. 비트 마스크를 작성하는 것은 쉽고 유지 관리가 어렵 기 때문입니다.

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

따라서 "Flags"에 대한 직접 액세스를 방지하기 위해 공용체 선언을 비공개로 설정하고 게터 / 세터 및 연산자 오버로드를 추가 한 다음 매크로를 작성해야합니다. Enum 으로이 작업을 수행하십시오.

불행히도 코드를 이식 가능하게하려면 A) 비트 레이아웃을 보장하거나 B) 컴파일 타임에 비트 레이아웃을 결정하는 방법이 없다고 생각합니다 (그래서 추적하고 최소한 변경 사항을 수정할 수 있습니다) 버전 / 플랫폼 등) 비트 필드가있는 구조체의 오프셋

런타임에 필드를 설정하고 어떤 비트가 변경되었는지 확인하기 위해 플래그를 XOR하는 트릭을 재생할 수 있습니다 .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;
}

정수 유형의 플래그 연산자가 작동합니다.


IMHO 이것이 최선의 답변입니다. 깨끗하고 간단하며 쉬운 클라이언트 구문. "constexpr uint8_t"대신 "const int"를 사용하지만 개념은 동일합니다.
yoyo

(죄송합니다, "constexpr int")
yoyo

3

현재 열거 형 플래그에 대한 언어 지원이 없습니다. 메타 클래스는이 기능이 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;
}

3

@Xaqq 사용 열거 플래그에 정말 좋은 타입 안전한 방법을 제공하고 여기에 a로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.
아우

1

위와 같이 (Kai) 다음을 수행하십시오. 실제로 열거 형은 "Enumerations"이며, 원하는 것은 세트가 있으므로 실제로 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

아마도 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.

왜 대답이 가장 적합한 지 설명 할 수 있습니까? 이 질문에 대한 다른 답변이 몇 개 있으므로 귀하의 정보를 차별화 할 수있는 정보를 포함하십시오.
trevorp
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.