#define, enum 또는 const를 사용해야합니까?


125

내가 작업하고있는 C ++ 프로젝트 에는 4 가지 값을 가질 수 있는 플래그 종류의 값이 있습니다. 이 네 가지 플래그를 결합 할 수 있습니다. 플래그는 데이터베이스의 레코드를 설명하며 다음과 같습니다.

  • 새로운 기록
  • 삭제 된 레코드
  • 수정 된 레코드
  • 기존 기록

이제 각 레코드에 대해이 속성을 유지하려고하므로 열거 형을 사용할 수 있습니다.

enum { xNew, xDeleted, xModified, xExisting }

그러나 코드의 다른 위치에서 사용자에게 표시 할 레코드를 선택해야하므로 다음과 같은 단일 매개 변수로 전달할 수 있습니다.

showRecords(xNew | xDeleted);

그래서 세 가지 가능한 접근법이있는 것 같습니다.

#define X_NEW      0x01
#define X_DELETED  0x02
#define X_MODIFIED 0x04
#define X_EXISTING 0x08

또는

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

또는

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

공간 요구 사항은 중요하지만 (바이트 대 int) 중요하지는 않습니다. 정의를 사용하면 형식 안전성이 enum떨어지고 일부 공간 (정수)이 손실되며 비트 단위 작업을 수행하려고 할 때 캐스팅해야합니다. 와 const나는에게도 잃게 유형의 안전을 생각하는 임의의 이후 uint8실수에 의해 얻을 수 있습니다.

다른 더 깨끗한 방법이 있습니까?

그렇지 않은 경우 무엇을 사용하고 왜 하시겠습니까?

추신 : 코드의 나머지 부분은 #defines가 없는 현대적인 C ++ 이며 일부 공간에서 네임 스페이스와 템플릿을 사용했기 때문에 의심의 여지가 없습니다.


"열거 형을 사용하면 공간이 부족합니다 (정수)". 반드시 그런 것은 아닙니다. stackoverflow.com/questions/366017/…stackoverflow.com/questions/1113855/… 및 gcc의 -fshort-enum을 참조하십시오 . (이 C 답변이 여전히 C ++에서는 사실이라고 가정합니다.)
idbrii

@pydave C와 C ++의 호환성에 대해 확신 할 수없는 경우이 링크가 매우 유용 하다고 생각
aka.nice

3
이것은 투표율이 높은 오래된 주제이므로이 문제 상황에 대해 C ++ 11 열거 형 클래스를 언급하지 않는 이유가 있습니다.
Brandin

참고로 비트 단위 연산자를 제공해야하지만 enum RecordType : uint8_t형식 안전성을 enum작은 크기와 결합합니다 uint8_t.
저스틴 타임 – 복원 모니카

답변:


88

단일 접근법의 단점을 줄이기 위해 전략을 결합하십시오. 임베디드 시스템에서 작업하므로 다음 솔루션은 정수 및 비트 연산자가 빠르고 메모리가 적으며 플래시 사용량이 적다는 사실을 기반으로합니다.

상수가 전역 네임 스페이스를 오염시키지 않도록 열거 형을 네임 스페이스에 배치하십시오.

namespace RecordType {

열거 형은 확인 된 유형의 컴파일 시간을 선언하고 정의합니다. 항상 컴파일 시간 유형 검사를 사용하여 인수 및 변수에 올바른 유형이 제공되도록하십시오. C ++에서는 typedef가 필요하지 않습니다.

enum TRecordType { xNew = 1, xDeleted = 2, xModified = 4, xExisting = 8,

유효하지 않은 상태의 다른 멤버를 작성하십시오. 오류 코드로 유용 할 수 있습니다. 예를 들어, 상태를 리턴하려고하지만 I / O 조작이 실패한 경우. 디버깅에도 유용합니다. 변수의 값을 사용해야하는지 여부를 알기 위해 초기화 목록 및 소멸자에서이를 사용하십시오.

xInvalid = 16 };

이 유형에는 두 가지 목적이 있습니다. 레코드의 현재 상태를 추적하고 특정 상태의 레코드를 선택하는 마스크를 만듭니다. 유형 값이 목적에 맞는지 테스트 할 인라인 함수를 작성하십시오. 상태 마커 대 상태 마스크로. (가) 이것은 버그를 잡을 것입니다 typedef단지 인 int과 같은 값으로 0xDEADBEEF초기화되지 않은 또는 mispointed 변수를 통해 변수에있을 수 있습니다.

inline bool IsValidState( TRecordType v) {
    switch(v) { case xNew: case xDeleted: case xModified: case xExisting: return true; }
    return false;
}

 inline bool IsValidMask( TRecordType v) {
    return v >= xNew  && v < xInvalid ;
}

using유형을 자주 사용하려면 지시문을 추가하십시오 .

using RecordType ::TRecordType ;

값 확인 기능은 잘못된 값을 사용하자마자 트랩하는 데 유용합니다. 달릴 때 벌레를 빨리 잡을수록 피해는 줄어 듭니다.

다음은이를 모두 정리 한 예입니다.

void showRecords(TRecordType mask) {
    assert(RecordType::IsValidMask(mask));
    // do stuff;
}

void wombleRecord(TRecord rec, TRecordType state) {
    assert(RecordType::IsValidState(state));
    if (RecordType ::xNew) {
    // ...
} in runtime

TRecordType updateRecord(TRecord rec, TRecordType newstate) {
    assert(RecordType::IsValidState(newstate));
    //...
    if (! access_was_successful) return RecordType ::xInvalid;
    return newstate;
}

올바른 값의 안전성을 보장하는 유일한 방법은 운영자 과부하가있는 전용 클래스를 사용하는 것이며 다른 독자에게는 연습으로 남습니다.


1
대부분 좋은 답변이지만 플래그를 결합 할 수 있으며 IsValidState () 함수는 플래그를 결합 할 수 없다고 질문합니다.
Jonathan Leffler

3
@Jonathan Leffler : 내가 서있는 곳에서 나는 'IsValidState'가 그렇게하지 않아야한다고 생각합니다. 'IsValidMask'입니다.
João Portela

1
IsValidMask없음 (예 :)을 선택할 수없는 것이 바람직한가요 0?
Joachim Sauer

2
−1 런타임 타입 검사의 아이디어는 혐오입니다.
건배와 hth. -Alf

54

정의를 잊어라

그들은 당신의 코드를 오염시킬 것입니다.

비트 필드?

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

절대로 사용하지 마십시오 . 경제적 인 4 정수보다 속도에 더 관심이 있습니다. 비트 필드를 사용하면 실제로 다른 유형에 액세스하는 것보다 느립니다.

그러나 구조체의 비트 멤버에는 실질적인 단점이 있습니다. 첫째, 메모리에서 비트 순서는 컴파일러마다 다릅니다. 또한 많은 인기있는 컴파일러는 비트 멤버를 읽고 쓰는 데 비효율적 인 코드를 생성 하며 대부분의 컴퓨터가 메모리의 임의 비트 세트를 조작 할 수 없기 때문에 비트 필드 (특히 멀티 프로세서 시스템)와 관련된 스레드 안전 문제가 심각하게 발생할 수 있습니다. 대신 전체 단어를로드하고 저장해야합니다. 예를 들어 다음은 뮤텍스를 사용하더라도 스레드 안전하지 않습니다.

출처 : http://en.wikipedia.org/wiki/Bit_field :

비트 필드를 사용 하지 않는 더 많은 이유가 필요한 경우 Raymond ChenThe Old New Thing Post : 비트 필드의 비용-이익 분석 에서 http://blogs.msdn.com/oldnewthing/을 참조하십시오. archive / 2008 / 11 / 26 / 9143050.aspx

const int?

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

네임 스페이스에 넣는 것은 멋지다. CPP 또는 헤더 파일에 선언 된 경우 해당 값이 인라인됩니다. 해당 값에서 스위치를 사용할 수는 있지만 커플 링이 약간 증가합니다.

아, 예 : 정적 키워드를 제거하십시오 . static은 C ++에서 더 이상 사용되지 않으며 uint8이 내장 유형 인 경우 동일한 모듈의 여러 소스에 포함 된 헤더에서 이것을 선언 할 필요가 없습니다. 결국 코드는 다음과 같아야합니다.

namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

이 접근법의 문제점은 코드가 상수 값을 알고 있으므로 커플 링이 약간 증가한다는 것입니다.

열거 형

다소 강한 타이핑을 가진 const int와 동일합니다.

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

그래도 여전히 글로벌 네임 스페이스를 오염시키고 있습니다. 그건 그렇고 ... typedef를 제거하십시오 . C ++로 작업 중입니다. 열거 형과 구조체의 typedef는 코드를 다른 무엇보다 오염시킵니다.

결과는 다음과 같습니다.

enum RecordType { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;

void doSomething(RecordType p_eMyEnum)
{
   if(p_eMyEnum == xNew)
   {
       // etc.
   }
}

보시다시피, 열거 형은 전역 네임 스페이스를 오염시키고 있습니다. 이 열거 형을 네임 스페이스에 넣으면 다음과 같은 것이 있습니다.

namespace RecordType {
   enum Value { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;
}

void doSomething(RecordType::Value p_eMyEnum)
{
   if(p_eMyEnum == RecordType::xNew)
   {
       // etc.
   }
}

extern const int?

커플 링을 줄이려면 (즉, 상수 값을 숨길 수 있으므로 전체 재 컴파일 없이도 원하는대로 수정) 정수를 헤더에서 extern으로, CPP 파일에서 상수로 선언 할 수 있습니다. 다음 예와 같이

// Header.hpp
namespace RecordType {
    extern const uint8 xNew ;
    extern const uint8 xDeleted ;
    extern const uint8 xModified ;
    extern const uint8 xExisting ;
}

과:

// Source.hpp
namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

그러나 이러한 상수에는 스위치를 사용할 수 없습니다. 결국, 독을 선택하십시오 ... :-p


5
왜 비트 필드가 느리다고 생각합니까? 실제로 코드와 다른 방법을 사용하여 코드를 프로파일 링했습니까? 그것이 그렇더라도, 명료 함은 속도보다 더 중요 할 수 있으며, "그것을 사용하지 마십시오"를 조금 단순화시킵니다.
wnoise

"정적 const uint8 xNew;" C ++ const 네임 스페이스 범위 변수에서 기본적으로 내부 연결로 인해 중복됩니다. "const"를 제거하면 외부 연결이 있습니다. 또한 "enum {...} RecordType;" 형식이 익명 열거 형인 "RecordType"이라는 전역 변수를 선언합니다.
bk1e

onebyone : 첫째, 주된 이유는 손실 (읽기 및 쓰기 둘 다에 대한 액세스 속도가 느림)에 의해 이득 (있는 경우 몇 바이트)이 무시되었다는 것입니다.
paercebal

3
onebyone : 둘째, 직장이나 가정에서 생산하는 모든 코드는 본질적으로 스레드 안전합니다. 쉬운 방법 : 전역 보호 기능, 정적 기능 없음, 잠금으로 보호되지 않는 한 스레드간에 공유되지 않음. 이 관용구를 사용하면이 기본 스레드 안전성이 손상됩니다. 그리고 무엇을 위해? 아마도 몇 바이트 일까? ... :-) ...
paercebal

비트 필드의 숨겨진 비용에 대한 Raymond Chen의 기사에 대한 참조가 추가되었습니다.
paercebal

30

std :: bitset을 배제 했습니까? 플래그 집합이 목적입니다. 하다

typedef std::bitset<4> RecordType;

그때

static const RecordType xNew(1);
static const RecordType xDeleted(2);
static const RecordType xModified(4);
static const RecordType xExisting(8);

비트 세트에 대한 연산자 오버로드가 많으므로 이제 할 수 있습니다

RecordType rt = whatever;      // unsigned long or RecordType expression
rt |= xNew;                    // set 
rt &= ~xDeleted;               // clear 
if ((rt & xModified) != 0) ... // test

또는 그것과 매우 유사한 것-이것을 테스트하지 않았으므로 정정에 감사드립니다. 인덱스별로 비트를 참조 할 수도 있지만 일반적으로 하나의 상수 세트 만 정의하는 것이 가장 좋으며 RecordType 상수가 더 유용 할 수 있습니다.

당신이 비트 세트를 배제했다고 가정하면, 나는 enum에 투표합니다 .

열거 형을 캐스팅하는 것이 심각한 단점이라는 것을 구입하지는 않습니다. 좋아요 약간 시끄럽고 열거 형에 범위를 벗어난 값을 지정하는 것은 정의되지 않은 동작이므로 이론적으로 비정상적인 C ++에서 발을 쏠 수 있습니다. 구현. 그러나 필요할 때만 수행하면 (int에서 enii iirc로 갈 때), 사람들이 전에 본 것과는 완전히 정상적인 코드입니다.

나는 열거 형의 공간 비용에 대해서도 모호합니다. uint8 변수와 매개 변수는 아마도 int보다 적은 스택을 사용하지 않을 것이므로 클래스의 저장소 만 중요합니다. 구조체에 여러 바이트를 패킹하는 경우가 있지만 (이 경우 uint8 스토리지에서 열거 형을 캐스팅 할 수 있음) 패딩이 이익을 죽일 수 있습니다.

따라서 열거 형은 다른 것에 비해 단점이 없으며 이점으로 약간의 유형 안전성 (명시 적으로 캐스팅하지 않고 임의의 정수 값을 할당 할 수 없음)과 모든 것을 참조하는 깨끗한 방법을 제공합니다.

우선적으로 나는 열거에 "= 2"를 넣을 것입니다. 꼭 필요한 것은 아니지만 "최소한 놀람의 원리"는 4 가지 정의 모두가 동일하게 보이도록 제안합니다.


1
실제로, 나는 비트 셋을 전혀 고려하지 않았다. 그러나 나는 그것이 좋을지 확신하지 못한다. 비트 세트를 사용하면 비트를 1, 2, 3, 4로 처리하여 코드를 읽을 수 없게 만듭니다. 즉, 열거 형을 사용하여 비트를 '이름'으로 지정합니다. 그래도 공간 절약이 될 수 있습니다. 감사.
밀라노 Babuškov

밀라노, 열거 형을 사용하여 비트의 이름을 "이름으로 지정할 필요"는 없으며 위에 표시된대로 미리 정의 된 비트 만 사용할 수 있습니다. my_bitset.flip (1) 대신 비트 1을 켜려면 my_bitset | = xNew;
moswald

이것은 당신에게 그리고 STL에서 더 많이 지시되지는 않지만, 정말로 묻습니다. 왜 bitset이것을 사용 하겠습니까? long어쨌든 각 요소에 대해 (내 구현에서 iirc; 예, 얼마나 낭비 적입니까) 또는 유사한 정수 유형 으로 변환됩니다 . (또는 현재 constexpr스토리지가 0 인 상태)
underscore_d

[편집 시간 초과] ...하지만 나는 bitset수업에 대한 이론적 근거를 실제로 이해하지 못했습니다. '웃음에 대한 토론에서 반복되는 저류가 아닌 것 같습니다. '
underscore_d

" uint8변수와 매개 변수는 아마도 ints" 보다 적은 스택을 사용하지 않을 것입니다 . 8 비트 레지스터가있는 CPU가있는 경우 int최소 2 개의 레지스터가 uint8_t필요하지만 1 개만 필요하므로 레지스터 가 부족할 가능성이 높기 때문에 스택 공간이 더 필요합니다 (더 느리고 코드 크기가 증가 할 수 있음 ( 명령어 세트에 따라)). (당신은 유형이, 그것이 있어야 uint8_t하지 uint8)
12,431,234,123,412,341,234,123


5

가능하면 매크로를 사용하지 마십시오. 현대 C ++에 관해서는 너무 감탄하지 않습니다.


4
진실. 내가 직접 매크로에 대해 싫어하는 것은 잘못된 경우 매크로에 들어갈 수 없다는 것입니다.
Carl

컴파일러에서 고칠 수 있다고 생각합니다.
celticminstrel

4

열거자는 유형 식별자뿐만 아니라 "식별자에 대한 의미"를 제공하므로 더 적합합니다. "xDeleted"가 "RecordType"이고 몇 년이 지난 후에도 "레코드 유형"(wow!)을 나타냅니다. Consts는 이에 대한 주석이 필요하며 코드에서 위아래로 이동해야합니다.


4

타입 안전을 잃어 버리는 것으로 정의

반드시 그런 것은 아닙니다 ...

// signed defines
#define X_NEW      0x01u
#define X_NEW      (unsigned(0x01))  // if you find this more readable...

열거 형으로 공간을 잃습니다 (정수)

반드시 그런 것은 아니지만 저장 지점에서 명시 적이어야합니다 ...

struct X
{
    RecordType recordType : 4;  // use exactly 4 bits...
    RecordType recordType2 : 4;  // use another 4 bits, typically in the same byte
    // of course, the overall record size may still be padded...
};

비트 연산을 원할 때 캐스팅해야합니다.

그로부터 고통을 없애기 위해 연산자를 만들 수 있습니다.

RecordType operator|(RecordType lhs, RecordType rhs)
{
    return RecordType((unsigned)lhs | (unsigned)rhs);
}

const를 사용하면 임의의 uint8이 실수로 들어갈 수 있기 때문에 유형 안전성도 손실됩니다.

범위와 값 검사는 일반적으로 형식 안전성과 직교합니다 (사용자 정의 형식 (예 : 자체 클래스)는 데이터에 대해 "불변"을 적용 할 수 있음). 열거 형을 사용하면 컴파일러는 값을 호스팅하기 위해 더 큰 유형을 자유롭게 선택할 수 있으며 초기화되지 않았거나 손상되었거나 미스 세트 열거 형 변수는 비트 패턴을 예상하지 않은 숫자로 해석 할 수 있습니다. 열거 식별자, 이들의 조합 및 0

다른 더 깨끗한 방법이 있습니까? / 그렇지 않다면 무엇을 사용하고 왜 사용합니까?

글쎄, 결국 시도되고 신뢰할 수있는 C 스타일 비트 OR 열거 형은 그림에 비트 필드와 사용자 지정 연산자가 있으면 잘 작동합니다. mat_geek의 답변에서와 같이 일부 사용자 정의 유효성 검사 기능 및 어설 션을 사용하여 견고성을 더욱 향상시킬 수 있습니다. 문자열, int, double 값 등을 처리하는 데 종종 동일하게 적용되는 기술

이것이 "깨끗하다"고 주장 할 수 있습니다.

enum RecordType { New, Deleted, Modified, Existing };

showRecords([](RecordType r) { return r == New || r == Deleted; });

나는 무관심하다 : 데이터 비트는 더 조밀하지만 코드는 크게 커진다 ... 당신이 가지고있는 객체의 수에 달려 있으며 lamdbas는 여전히 비트 OR보다 더 지저분하고 어렵습니다.

BTW /-스레드 안전성의 매우 약한 IMHO에 대한 주장-지배적 의사 결정 추진력이 아닌 배경 고려 사항으로 가장 잘 기억됩니다. 패킹을 모르는 경우에도 비트 필드에서 뮤텍스를 공유하는 것이 좋습니다 (뮤텍스는 상대적으로 부피가 큰 데이터 멤버입니다. 한 객체의 멤버에 여러 개의 멀티 플렉스를 갖는 것을 고려할 때 성능에 대해 정말로 걱정해야합니다. 비트 필드임을 알 수 있습니다.) 하위 단어 크기 유형은 모두 같은 문제가있을 수 있습니다 (예 :) uint8_t. 어쨌든 높은 동시성에 필사적 인 경우 원자 적 비교 및 ​​스왑 스타일 작업을 시도 할 수 있습니다.


1
벌금 +1 그러나 명령 전에 operator|정수 유형 ( unsigned int)으로 캐스트해야합니다 |. 그렇지 않으면 operator|재귀 적으로 호출되어 런타임 스택 오버플로가 발생합니다. 나는 제안한다 : return RecordType( unsigned(lhs) | unsigned(rhs) );. 건배
올리버

3

열거 형을 저장하기 위해 4 바이트를 사용해야하는 경우에도 (C ++에 익숙하지 않습니다-C #에서 기본 유형을 지정할 수 있다는 것을 알고 있습니다), 여전히 가치가 있습니다-열거 형을 사용하십시오.

요즘 GB의 메모리를 가진 서버의 시대에 응용 프로그램 수준에서 일반적으로 4 바이트 대 1 바이트의 메모리는 중요하지 않습니다. 물론 특정 상황에서 메모리 사용이 중요하다면 (그리고 C ++에서 바이트를 사용하여 열거 형을 뒷받침 할 수없는 경우) '정적 const'경로를 고려할 수 있습니다.

하루가 끝나면 데이터 구조의 3 바이트 메모리 절약을 위해 '정적 const'를 사용하는 것이 유지 보수 적격 가치가 있습니까?

IIRC는 x86에서 데이터 구조가 4 바이트 단위로 정렬되므로 '레코드'구조에 여러 바이트 너비 요소가 없으면 실제로 중요하지 않을 수 있습니다. 성능 / 공간의 유지 보수성에있어 타협하기 전에 테스트하고 수행하십시오.


언어 개정 C ++ 11부터 C ++에서 기본 유형을 지정할 수 있습니다. 그때까지는 "지정된 모든 열거 자에 대해 비트 필드로 저장하고 사용할 수있을만큼 충분히 크지 만 int너무 작지 않은 경우"라고 생각합니다. [C ++ 11에서 기본 유형을 지정하지 않으면 레거시 동작을 사용합니다. 반대로, C ++ 11 enum class의 기본 유형은 int달리 지정되지 않은 경우 기본적으로 기본적으로 설정됩니다.]
Justin Time-Reinstate Monica

3

열거 구문과 비트 검사의 편의로 클래스의 형식 안전성을 원한다면 C ++의 안전 레이블을 고려하십시오 . 저자와 함께 일한 그는 꽤 똑똑합니다.

그래도 조심하십시오. 결국이 패키지는 템플릿 매크로를 사용 합니다!


내 작은 응용 프로그램에 대한 잔인한 것 같습니다. 그러나 좋은 해결책처럼 보입니다.
Milan Babuškov

2

실제로 플래그 값을 개념적 전체로 전달해야합니까, 아니면 플래그 당 코드가 많이 있습니까? 어느 쪽이든, 나는 이것을 1 비트 비트 필드의 클래스 또는 구조체로 갖는 것이 실제로 더 분명하다고 생각합니다.

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

그런 다음 레코드 클래스에는 struct RecordFlag 멤버 변수가 있고 함수는 struct RecordFlag 유형의 인수를 사용할 수 있습니다. 컴파일러는 비트 필드를 함께 묶어 공간을 절약해야합니다.


때로는 전체적으로, 때로는 깃발로도 사용됩니다. 또한 특정 플래그가 설정되어 있는지 테스트해야합니다 (전체적으로 전달할 때).
밀라노 Babuškov

글쎄, 별도의 경우, 그냥 int를 요청하십시오. 함께하면 구조체를 전달하십시오.
wnoise

더 나아지지 않을 것입니다. 비트 필드에 대한 액세스는 다른 것보다 느립니다.
paercebal

정말? 컴파일러가 수동 비트 트위들 링과 비트 필드 테스트를 위해 크게 다른 코드를 생성 할 것이라고 생각하십니까? 그리고 그것이 훨씬 느려질까요? 왜? 관용적으로 쉽게 할 수없는 한 가지는 여러 플래그를 한 번에 마스크하는 것입니다.
wnoise

간단한 읽기 테스트를 실행하면 비트 마스킹의 경우 5.50-5.58 초와 비트 필드 액세스의 경우 5.45-5.59가 나타납니다. 거의 구별 할 수 없습니다.
wnoise

2

아마도 값을 함께 결합 할 수있는 이런 종류의 열거 형을 사용하지 않을 것입니다. 일반적으로 열거 형은 상호 배타적 인 상태입니다.

그러나 어떤 방법을 사용하든 이들 값이 함께 결합 될 수있는 비트임을 분명히하기 위해 실제 값에이 구문을 사용하십시오.

#define X_NEW      (1 << 0)
#define X_DELETED  (1 << 1)
#define X_MODIFIED (1 << 2)
#define X_EXISTING (1 << 3)

왼쪽 시프트를 사용하면 각 값이 단일 비트임을 의미하며 나중에 누군가 새 값을 추가하고 값을 9로 지정하는 등의 일이 잘못 될 가능성이 적습니다.


1
특히 ioctl ()에 대한 상수에는 충분한 선례가 있습니다. 0x01로, 0x02로,를 0x04, 0x08에서, 0x10을 ... : 나는 비록 진수 상수를 사용하는 것을 선호
조나단 레플러

2

KISS , 높은 응집력 및 낮은 커플 링을 바탕으로 다음과 같은 질문을하십시오.

  • 누가 알아야합니까? 내 수업, 내 도서관, 다른 수업, 다른 도서관, 타사
  • 어떤 수준의 추상화를 제공해야합니까? 소비자는 비트 연산을 이해합니까?
  • VB / C # 등에서 인터페이스해야합니까?

" Large-Scale C ++ Software Design " 이라는 훌륭한 책 이 있습니다. 다른 헤더 파일 / 인터페이스 종속성을 피할 수 있다면 외부에서 기본 유형을 승격시킵니다.


1
a) 5-6 수업. b) 나만, 그것은 한 사람의 프로젝트입니다. c) 인터페이스가 없습니다
Milan Babuškov

2

Qt를 사용하는 경우 QFlags찾아야 합니다. QFlags 클래스는 열거 형 값의 OR 조합을 저장하는 안전한 형식의 방법을 제공합니다.


아니, Qt. 실제로, 그것은 wxWidgets 프로젝트입니다.
Milan Babuškov

0

차라리 갈래

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

간단히 말해서 :

  1. 깨끗하고 코드를 읽고 유지 관리 할 수 ​​있습니다.
  2. 논리적으로 상수를 그룹화합니다.
  3. 3 바이트를 절약하는 것이 아니라면 프로그래머의 시간이 더 중요 합니다.

글쎄, 나는 백만 명의 클래스 레코드를 쉽게 가질 수 있으므로 중요 할 수 있습니다. OTOH, 그것은 1MB와 4MB의 차이점 일 뿐이므로 걱정하지 않아도됩니다.
밀라노 Babuškov

@Vivek : 정수 너비 제한을 고려 했습니까? 특히 C ++ 11 이전.
user2672165

0

나는 모든 것을 지나치게 엔지니어링하고 싶지는 않지만 때로는이 경우이 정보를 캡슐화하기 위해 (작은) 클래스를 만들 가치가 있습니다. RecordType 클래스를 작성하면 다음과 같은 기능이있을 수 있습니다.

무효 setDeleted ();

무효 clearDeleted ();

부울 isDeleted ();

등 ... (또는 모든 컨벤션에 적합)

조합의 유효성을 검사 할 수 있습니다 (예 : '신규'와 '삭제됨'을 동시에 설정할 수없는 경우와 같이 모든 조합이 합법적이지 않은 경우). 비트 마스크 등을 사용한 경우 상태를 설정하는 코드의 유효성을 검사해야하며 클래스는 해당 논리도 캡슐화 할 수 있습니다.

클래스는 또한 각 상태에 의미있는 로깅 정보를 첨부하는 기능을 제공 할 수 있으며 현재 상태 등의 문자열 표현을 반환하는 함수를 추가 할 수 있습니다 (또는 스트리밍 연산자 '<<').

스토리지에 대해 걱정이 되더라도 클래스에 'char'데이터 멤버 만있을 수 있으므로 적은 양의 스토리지 만 사용하십시오 (가상이 아닌 경우). 물론 하드웨어 등에 따라 정렬 문제가있을 수 있습니다.

실제 비트 값이 헤더 파일이 아닌 cpp 파일 내부의 익명 네임 스페이스에있는 경우 나머지 '세계'에 표시되지 않을 수 있습니다.

enum / # define / bitmask 등을 사용하는 코드에 유효하지 않은 조합, 로깅 등을 처리하는 많은 '지원'코드가있는 경우 클래스의 캡슐화를 고려할 가치가 있습니다. 물론 대부분의 경우 간단한 솔루션으로 간단한 문제를 해결하는 것이 좋습니다.


불행히도 선언은 프로젝트 전체에서 사용되기 때문에 .h 파일에 있어야합니다 (약 5-6 클래스에서 사용).
Milan Babuškov
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.