#define, enum 또는 const를 사용해야합니까?
내가 작업하고있는 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
실수에 의해 얻을 수 있습니다.
다른 더 깨끗한 방법이 있습니까?
그렇지 않은 경우 무엇을 사용하고 왜 하시겠습니까?
추신 : 코드의 나머지 부분은 #define
s가 없는 현대적인 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 Chen 은 The 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 상수가 더 유용 할 수 있습니다.
Assuming you have ruled out bitset, I vote for the enum.
I don't buy that casting the enums is a serious disadvantage - OK so it's a bit noisy, and assigning an out-of-range value to an enum is undefined behaviour so it's theoretically possible to shoot yourself in the foot on some unusual C++ implementations. But if you only do it when necessary (which is when going from int to enum iirc), it's perfectly normal code that people have seen before.
I'm dubious about any space cost of the enum, too. uint8 variables and parameters probably won't use any less stack than ints, so only storage in classes matters. There are some cases where packing multiple bytes in a struct will win (in which case you can cast enums in and out of uint8 storage), but normally padding will kill the benefit anyhow.
So the enum has no disadvantages compared with the others, and as an advantage gives you a bit of type-safety (you can't assign some random integer value without explicitly casting) and clean ways of referring to everything.
For preference I'd also put the "= 2" in the enum, by the way. It's not necessary, but a "principle of least astonishment" suggests that all 4 definitions should look the same.
Here are couple of articles on const vs. macros vs. enums:
Symbolic Constants
Enumeration Constants vs. Constant Objects
I think you should avoid macros especially since you wrote most of your new code is in modern C++.
If possible do NOT use macros. They aren't too much admired when it comes to modern C++.
Enums would be more appropriate as they provide "meaning to the identifiers" as well as type safety. You can clearly tell "xDeleted" is of "RecordType" and that represent "type of a record" (wow!) even after years. Consts would require comments for that, also they would require going up and down in code.
With defines I lose type safety
Not necessarily...
// signed defines
#define X_NEW 0x01u
#define X_NEW (unsigned(0x01)) // if you find this more readable...
and with enum I lose some space (integers)
Not necessarily - but you do have to be explicit at points of storage...
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...
};
and probably have to cast when I want to do bitwise operation.
You can create operators to take the pain out of that:
RecordType operator|(RecordType lhs, RecordType rhs)
{
return RecordType((unsigned)lhs | (unsigned)rhs);
}
With const I think I also lose type safety since a random uint8 could get in by mistake.
The same can happen with any of these mechanisms: range and value checks are normally orthogonal to type safety (though user-defined-types - i.e. your own classes - can enforce "invariants" about their data). With enums, the compiler's free to pick a larger type to host the values, and an uninitialised, corrupted or just miss-set enum variable could still end up interpretting its bit pattern as a number you wouldn't expect - comparing unequal to any of the enumeration identifiers, any combination of them, and 0.
Is there some other cleaner way? / If not, what would you use and why?
Well, in the end the tried-and-trusted C-style bitwise OR of enumerations works pretty well once you have bit fields and custom operators in the picture. You can further improve your robustness with some custom validation functions and assertions as in mat_geek's answer; techniques often equally applicable to handling string, int, double values etc..
You could argue that this is "cleaner":
enum RecordType { New, Deleted, Modified, Existing };
showRecords([](RecordType r) { return r == New || r == Deleted; });
I'm indifferent: the data bits pack tighter but the code grows significantly... depends how many objects you've got, and the lamdbas - beautiful as they are - are still messier and harder to get right than bitwise ORs.
BTW /- the argument about thread safety's pretty weak IMHO - best remembered as a background consideration rather than becoming a dominant decision-driving force; sharing a mutex across the bitfields is a more likely practice even if unaware of their packing (mutexes are relatively bulky data members - I have to be really concerned about performance to consider having multiple mutexes on members of one object, and I'd look carefully enough to notice they were bit fields). Any sub-word-size type could have the same problem (e.g. a uint8_t
). Anyway, you could try atomic compare-and-swap style operations if you're desperate for higher concurrency.
Even if you have to use 4 byte to store an enum (I'm not that familiar with C++ -- I know you can specify the underlying type in C#), it's still worth it -- use enums.
In this day and age of servers with GBs of memory, things like 4 bytes vs. 1 byte of memory at the application level in general don't matter. Of course, if in your particular situation, memory usage is that important (and you can't get C++ to use a byte to back the enum), then you can consider the 'static const' route.
At the end of the day, you have to ask yourself, is it worth the maintenance hit of using 'static const' for the 3 bytes of memory savings for your data structure?
Something else to keep in mind -- IIRC, on x86, data structures are 4-byte aligned, so unless you have a number of byte-width elements in your 'record' structure, it might not actually matter. Test and make sure it does before you make a tradeoff in maintainability for performance/space.
If you want the type safety of classes, with the convenience of enumeration syntax and bit checking, consider Safe Labels in C++. I've worked with the author, and he's pretty smart.
Beware, though. In the end, this package uses templates and macros!
Do you actually need to pass around the flag values as a conceptual whole, or are you going to have a lot of per-flag code? Either way, I think having this as class or struct of 1-bit bitfields might actually be clearer:
struct RecordFlag {
unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};
Then your record class could have a struct RecordFlag member variable, functions can take arguments of type struct RecordFlag, etc. The compiler should pack the bitfields together, saving space.
I probably wouldn't use an enum for this kind of a thing where the values can be combined together, more typically enums are mutually exclusive states.
But whichever method you use, to make it more clear that these are values which are bits which can be combined together, use this syntax for the actual values instead:
#define X_NEW (1 << 0)
#define X_DELETED (1 << 1)
#define X_MODIFIED (1 << 2)
#define X_EXISTING (1 << 3)
Using a left-shift there helps to indicate that each value is intended to be a single bit, it is less likely that later on someone would do something wrong like add a new value and assign it something a value of 9.
Based on KISS, high cohesion and low coupling, ask these questions -
- Who needs to know? my class, my library, other classes, other libraries, 3rd parties
- What level of abstraction do I need to provide? Does the consumer understand bit operations.
- Will I have have to interface from VB/C# etc?
There is a great book "Large-Scale C++ Software Design", this promotes base types externally, if you can avoid another header file/interface dependancy you should try to.
If you are using Qt you should have a look for QFlags. The QFlags class provides a type-safe way of storing OR-combinations of enum values.
I would rather go with
typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;
Simply because:
- It is cleaner and it makes the code readable and maintainable.
- It logically groups the constants.
- Programmer's time is more important, unless your job is to save those 3 bytes.
Not that I like to over-engineer everything but sometimes in these cases it may be worth creating a (small) class to encapsulate this information. If you create a class RecordType then it might have functions like:
void setDeleted();
void clearDeleted();
bool isDeleted();
etc... (or whatever convention suits)
It could validate combinations (in the case where not all combinations are legal, eg if 'new' and 'deleted' could not both be set at the same time). If you just used bit masks etc then the code that sets the state needs to validate, a class can encapsulate that logic too.
The class may also give you the ability to attach meaningful logging info to each state, you could add a function to return a string representation of the current state etc (or use the streaming operators '<<').
For all that if you are worried about storage you could still have the class only have a 'char' data member, so only take a small amount of storage (assuming it is non virtual). Of course depending on the hardware etc you may have alignment issues.
You could have the actual bit values not visible to the rest of the 'world' if they are in an anonymous namespace inside the cpp file rather than in the header file.
If you find that the code using the enum/#define/ bitmask etc has a lot of 'support' code to deal with invalid combinations, logging etc then encapsulation in a class may be worth considering. Of course most times simple problems are better off with simple solutions...
참고URL : https://stackoverflow.com/questions/112433/should-i-use-define-enum-or-const
'IT박스' 카테고리의 다른 글
MySQL은 where 절과 결합 (0) | 2020.07.12 |
---|---|
파일 또는 어셈블리 'System.Data.SQLite'를로드 할 수 없습니다 (0) | 2020.07.12 |
파이썬 'type'객체를 문자열로 변환 (0) | 2020.07.11 |
Objective-C 대신 Cocoa와 함께 C ++를 사용 하시겠습니까? (0) | 2020.07.11 |
SQL Server SELECT LAST N 행 (0) | 2020.07.11 |