C, правильное использование перечислений, объединений и возврата функций

Моя цель - добиться лучших практик кода/дизайна:

У меня есть функции, которые возвращают 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 [я не проверял это, так как я почти не использовал объединение, но я ожидаю, что это сработает] сократил мою работу, чтобы скопировать перечисление из другого модуля в этот модуль.. но мне все еще нужно убедиться, что (состояние перечисления) не перекрывается ( перечисление ошибок) ..

  • что вы думаете о коде выше
  • что посоветуете еще улучшить?

Лучше избегать enum особенно в структурах и союзах.

i486 15.12.2020 10:39

@ i486 i486 для чистого кода, что тогда лучше всего использовать для констант? я читал, что другие говорят, что я не должен использовать константы или определения макросов...

Hasan alattar 15.12.2020 10:42
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
115
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Мне нравится думать о перечислениях как о наборе (списке) констант. Если мне нужно сохранить состояние, я ввожу новый тип, например. битсет_т или состояние_т.

Пример:

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 бит?

Hasan alattar 15.12.2020 11:48

Я добавил пример набора битов, который позволяет хранить «неограниченное» количество значений.

Erdal Küçük 15.12.2020 12:38

Другие вопросы по теме