Моя цель - добиться лучших практик кода/дизайна:
У меня есть функции, которые возвращают 2 возможных перечисления, которые могут быть (перечисление состояний) или (перечисление ошибок).
чтобы различать два перечисления, они должны быть далеки друг от друга и никогда не перекрываться, например:
другие модули имеют функцию, ожидающую (перечисление ошибок). и я не хочу делать функцию отображения из состояния в ошибку. поэтому я должен содержать (ошибку перечисления) внутри (состояние перечисления).
Enum eSomeState{
enum1Value = 0,
...
enum1Value99 = 99, // <<max
};
//defined in different module:
Enum eSomeError{
enum2Value = 100, // <<min
...
enum2Value99 = 199,
};
так как они перекрываются, невозможно будет узнать, какой из них был возвращен из вызванной функции (без дополнительного флага).
Итак, ранее в моем коде я определял два перечисления (объединяя все ошибки в перечислении ошибок) в (перечисление состояний).. как показано ниже.
Версия 1:
/* used by this module */
enum eSomeState{
eSomeState_A = 1,
eSomeState_B = 2,
eSomeState_C = 3,
eSomeState_D = 4,
/* do not reach 100 */
/* DO NOT GO BELOW 100 */ <------------- copied from other module's enum
/* every time i update eSomeErrorFromOtherModule, i have to come here and update this as well */
eSomeStateError_ErrA = 100,
eSomeStateError_ErrB = 101,
eSomeStateError_ErrC = 102,
};
/* used by this module and other module that cares about error handling */
/* defined in different scope */
enum eSomeErrorFromOtherModule{
eSomeError_none = 0,
/* DO NOT GO BELOW 100 */
eSomeError_ErrA = 100,
eSomeError_ErrB = 101,
eSomeError_ErrC = 102,
};
bool doSomthingA( enum eSomeState* const state){
Assert(*state == eSomeState_A);
//do stuff
if (success){
*state = eSomeState_B;
return true;
}else{
*state = err;
}
return false;
}
функции из других модулей ожидают тип (Error Enum), поэтому я использовал (State Enum) для (Error Enum)
через некоторое время я заметил, что трудно продолжать копировать (Error Enum) в (State Enum) и сохранять их идентичными каждый раз, когда мне нужно изменить один из них.
поэтому я перешел на версию 2:
/* used by this module */
enum eSomeState{
eSomeState_A = 1,
eSomeState_B = 2,
eSomeState_C = 3,
eSomeState_D = 4,
/* do not reach 100 */
}
union uSomeState{
enum eSomeState state;
enum eSomeErrorFromOtherModule err;
}
/* functions updated to */
bool doSomthingA( union eSomeState* const state){
Assert(state->state == eSomeState_A);
//do stuff
if (success){
state->state = eSomeState_B;
return true;
}else{
state->err = err;
}
return false;
}
версия 2 [я не проверял это, так как я почти не использовал объединение, но я ожидаю, что это сработает] сократил мою работу, чтобы скопировать перечисление из другого модуля в этот модуль.. но мне все еще нужно убедиться, что (состояние перечисления) не перекрывается ( перечисление ошибок) ..
@ i486 i486 для чистого кода, что тогда лучше всего использовать для констант? я читал, что другие говорят, что я не должен использовать константы или определения макросов...
Мне нравится думать о перечислениях как о наборе (списке) констант. Если мне нужно сохранить состояние, я ввожу новый тип, например. битсет_т или состояние_т.
Пример:
typedef int state_t;
enum someStates {};
enum someErrorStates {};
bool doSomething(state_t state) {}
Потому что иногда необходимо «объединить» два состояния вместе (например, оператором «|»), которого нет ни в одном из перечислений.
Просто мнение.
Добавка:
По сути, перечисление представляет собой целое число (беззнаковое?), и вы можете хранить значения до INT_MAX в одном наборе. Но если вам нужен «битовый набор», вы ограничены 32 (если int 32 бита) различными значениями. Если вы хотите иметь больше, вам нужно реализовать набор битов, массив слов.
Пример:
#define word_size sizeof(unsigned int)
#define word_width (word_size * CHAR_BIT) // defined in limits.h
#define word_index(Pos) ((Pos) / word_width)
#define bit_index(Pos) ((Pos) % word_width)
unsigned int bitset[N];
bool isSet(unsigned int bs[N], unsigned int pos)
{
return ((bs[word_index(pos)] >> bit_index(pos)) & 1);
}
void setBit(unsigned int bs[N], unsigned int pos)
{
bs[word_index(pos)] |= (1 << bit_index(pos));
}
void clearBit(unsigned int bs[N], unsigned int pos)
{
bs[word_index(pos)] &= ~(1 << bit_index(pos));
}
void toggleBit(unsigned int bs[N], unsigned int pos)
{
bs[word_index(pos)] ^= (1 << bit_index(pos));
}
И тогда вы можете определить свои перечисления следующим образом:
enum someState {
state1 = 1,
state2 = 2
//...
};
setBit(bitset, state2);
isSet(bitset, state2);
// ... and so on
По сути, константы перечисления будут описывать (битовую) позицию в наборе битов.
привет, спасибо, что поделились, что бы вы сделали, если количество (ошибок + состояний) больше 32/64 бит?
Я добавил пример набора битов, который позволяет хранить «неограниченное» количество значений.
Лучше избегать
enum
особенно в структурах и союзах.