#define, enum 또는 const를 사용해야합니까? xModified, xExisting } 그러나

내가 작업하고있는 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 ++ 이며 일부 공간에서 네임 스페이스와 템플릿을 사용했기 때문에 의심의 여지가 없습니다.



답변

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

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

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

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


답변

정의를 잊어라

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

비트 필드?

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


답변

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 가지 정의 모두가 동일하게 보이도록 제안합니다.


답변

const vs. macros와 enums에 관한 기사는 다음과 같습니다.

기호 상수

열거 상수 대 상수 개체

새로운 코드의 대부분은 현대 C ++로 작성되었으므로 매크로를 피해야한다고 생각합니다.


답변

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


답변

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


답변

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

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

// 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. 어쨌든 높은 동시성에 필사적 인 경우 원자 적 비교 및 ​​스왑 스타일 작업을 시도 할 수 있습니다.