문제

제가 작업 중인 C++ 프로젝트에는 깃발 네 가지 값을 가질 수 있는 종류의 값입니다.이 네 개의 플래그는 결합될 수 있습니다.플래그는 데이터베이스의 레코드를 설명하며 다음과 같습니다.

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

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

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

공간 요구 사항은 중요하지만(바이트 대 정수) 중요하지는 않습니다.정의를 사용하면 유형 안전성이 떨어지고 enum 일부 공간(정수)이 손실되고 비트 단위 연산을 수행하려면 캐스팅해야 할 수도 있습니다.와 함께 const 무작위 이후로 유형 안전성도 상실한 것 같습니다. uint8 실수로 들어갈 수도 있어요.

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

그렇지 않다면 무엇을 사용하고 그 이유는 무엇입니까?

추신나머지 코드는 코드가 없는 깔끔한 최신 C++입니다. #defines, 그리고 저는 몇몇 공간에서 네임스페이스와 템플릿을 사용했기 때문에 그것도 문제가 되지 않습니다.

도움이 되었습니까?

해결책

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

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

namespace RecordType {

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

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

잘못된 상태에 대해 다른 구성원을 만듭니다.이는 오류 코드로 유용할 수 있습니다.예를 들어 상태를 반환하려고 하는데 I/O 작업이 실패하는 경우입니다.디버깅에도 유용합니다.변수의 값을 사용해야 하는지 확인하려면 초기화 목록과 소멸자에서 이를 사용하세요.

xInvalid = 16 };

이 유형에는 두 가지 목적이 있다고 생각하세요.레코드의 현재 상태를 추적하고 특정 상태의 레코드를 선택하기 위한 마스크를 생성합니다.유형 값이 목적에 유효한지 테스트하는 인라인 함수를 만듭니다.상태 마커와 상태 마스크로 사용됩니다.이렇게 하면 버그를 잡을 수 있습니다. typedef 단지 int 그리고 다음과 같은 값 0xDEADBEEF 초기화되지 않았거나 잘못 지정한 변수를 통해 변수에 있을 수 있습니다.

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:

그리고 더 많은 이유가 필요하다면 ~ 아니다 아마도 비트 필드를 사용하십시오 레이먼드 첸 그의 말에 당신을 확신시킬 것입니다 오래된 새로운 것 우편: 부울 컬렉션에 대한 비트 필드의 비용 편익 분석 ~에 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;

하지만 그들은 여전히 ​​전역 네임스페이스를 오염시키고 있습니다.그런데... 형식 정의를 제거하세요..당신은 C++로 작업하고 있습니다.열거형과 구조체의 형식 정의는 무엇보다 코드를 오염시킵니다.

결과는 다음과 같습니다.

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

외부 const int ?

결합을 줄이려는 경우(예:상수 값을 숨길 수 있으므로 전체 재컴파일 없이 원하는 대로 수정할 수 있으므로 다음 예와 같이 헤더에서 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;
}

하지만 해당 상수에는 스위치를 사용할 수 없습니다.그러니 결국 독을 골라라...:-피

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 상수가 더 유용할 것입니다.

당신이 bitset을 배제했다고 가정하면, 나는 다음에 투표합니다. 열거형.

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

열거형의 공간 비용에 대해서도 의심스럽습니다.uint8 변수와 매개변수는 아마도 int보다 적은 스택을 사용하지 않을 것이므로 클래스의 저장 공간만 중요합니다.구조체에 여러 바이트를 패킹하는 것이 승리하는 경우도 있지만(이 경우 uint8 저장소 안팎으로 열거형을 캐스팅할 수 있음) 일반적으로 패딩을 사용하면 어쨌든 이점이 사라집니다.

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

그런데 저는 선호도에 따라 열거형에 "= 2"도 넣었습니다.꼭 필요한 것은 아니지만 "놀라움을 최소화하는 원칙"은 4가지 정의가 모두 동일하게 보여야 함을 의미합니다.

다음은 const와 const에 대한 몇 가지 기사입니다.매크로 대열거형:

기호 상수
열거 상수와상수 객체

특히 새 코드의 대부분을 최신 C++로 작성했기 때문에 매크로를 피해야 한다고 생각합니다.

가능하다면 매크로를 사용하지 마십시오.현대 C++에 관해서는 그들은 그다지 존경받지 않습니다.

열거형은 유형 안전성뿐만 아니라 "식별자에 대한 의미"를 제공하므로 더 적합합니다."xDeleted"가 "RecordType"이고 몇 년이 지난 후에도 "레코드 유형"(와우!)을 나타낸다는 것을 분명히 알 수 있습니다.Consts에는 이에 대한 주석이 필요하며 코드에서 위아래로 이동해야 합니다.

정의를 사용하면 유형 안전성이 상실됩니다.

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

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

enum을 사용하면 공간(정수)이 손실됩니다.

꼭 그런 것은 아니지만 저장 지점을 명시적으로 명시해야 합니다.

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보다 더 지저분하고 제대로 얻기가 어렵습니다.

그런데 / - 스레드 안전성의 매우 약한 IMHO에 대한 주장 - 지배적인 의사 결정 원동력이 되기보다는 배경 고려 사항으로 가장 잘 기억됩니다.비트 필드 전체에서 뮤텍스를 공유하는 것은 패킹을 인식하지 못하더라도 더 가능성이 높은 관행입니다(뮤텍스는 상대적으로 부피가 큰 데이터 멤버입니다. 한 객체의 멤버에 여러 뮤텍스를 갖는 것을 고려하려면 성능에 대해 정말로 걱정해야 하며 주의 깊게 살펴보겠습니다. 비트 필드라는 것을 알 수 있을 만큼 충분합니다).모든 하위 단어 크기 유형에는 동일한 문제가 있을 수 있습니다(예:ㅏ uint8_t).어쨌든, 더 높은 동시성이 절실히 필요하다면 원자적 비교 및 ​​교환 스타일 작업을 시도해 볼 수 있습니다.

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

오늘날 GB 메모리를 갖춘 서버 시대에는 4바이트와 4바이트 같은 것이 있습니다.일반적으로 애플리케이션 수준의 1바이트 메모리는 중요하지 않습니다.물론, 특정 상황에서 메모리 사용량이 중요하다면(그리고 C++에서 열거형을 지원하기 위해 바이트를 사용하도록 할 수 없는 경우) 'static const' 경로를 고려해 볼 수 있습니다.

결국에는 데이터 구조의 3바이트 메모리 절약을 위해 'static const'를 사용하여 유지 관리를 수행할 가치가 있는지 스스로에게 물어봐야 합니다.

염두에 두어야 할 또 다른 사항 - x86의 IIRC 데이터 구조는 4바이트로 정렬되므로 '레코드' 구조에 바이트 너비 요소 수가 없으면 실제로 중요하지 않을 수 있습니다.성능/공간에 대한 유지 관리 가능성을 절충하기 전에 테스트하고 확인하십시오.

편리한 열거형 구문과 비트 검사를 통해 클래스의 유형 안전성을 원한다면 다음을 고려하세요. C++의 안전한 레이블.나는 그 작가와 함께 일한 적이 있는데, 그는 꽤 똑똑해요.

하지만 조심하세요.결국 이 패키지는 템플릿을 사용합니다. 그리고 매크로!

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

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

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

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

그러나 어떤 방법을 사용하든 이러한 값이 함께 결합될 수 있는 비트라는 점을 더 명확하게 하려면 대신 실제 값에 대해 다음 구문을 사용하십시오.

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

왼쪽 시프트를 사용하면 각 값이 단일 비트로 의도되었음을 나타내는 데 도움이 되며, 나중에 누군가가 새 값을 추가하고 값 9를 할당하는 등 잘못된 작업을 수행할 가능성이 줄어듭니다.

기반 키스, 높은 응집력과 낮은 결합력, 다음 질문을 해보세요 -

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

좋은 책이 있어요 "대규모 C++ 소프트웨어 설계", 시도해야 할 다른 헤더 파일/인터페이스 종속성을 피할 수 있는 경우 기본 유형을 외부적으로 승격합니다.

Qt를 사용하고 있다면 다음을 살펴보십시오. QFlags.QFlags 클래스는 열거형 값의 OR 조합을 저장하는 유형이 안전한 방법을 제공합니다.

차라리 같이 갈래

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

간단히 말해서:

  1. 이는 더 깔끔하며 코드를 읽기 쉽고 유지 관리하기 쉽게 만듭니다.
  2. 상수를 논리적으로 그룹화합니다.
  3. 직업이 아닌 이상 프로그래머의 시간이 더 중요합니다. ~이다 3바이트를 저장합니다.

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

무효 setDeleted();

무효 삭제();

bool isDeleted();

등...(또는 어떤 컨벤션에 적합하든)

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

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

저장소가 걱정된다면 클래스에 'char' 데이터 멤버만 갖도록 할 수 있으므로 소량의 저장소만 사용하십시오(가상이 아니라고 가정).물론 하드웨어 등에 따라 정렬 문제가 있을 수 있습니다.

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

열거형/#define/비트마스크 등을 사용하는 코드에 유효하지 않은 조합, 로깅 등을 처리하기 위한 '지원' 코드가 많다면 클래스의 캡슐화를 고려해 볼 가치가 있습니다.물론 대부분의 경우 간단한 문제는 간단한 솔루션을 사용하는 것이 더 좋습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top