Подтвердить что ты не робот

Использование значения по умолчанию в инструкции switch при переходе перечисления

Какова ваша процедура при переключении перечисления, где каждое перечисление покрывается случаем? В идеале вы хотели бы, чтобы код был в будущем доказательством, как вы это делаете?

Кроме того, что, если какой-то идиот бросает произвольный int в тип перечисления? Следует ли рассматривать эту возможность? Или мы должны предположить, что такая вопиющая ошибка попала бы в обзор кода?

enum Enum
{
    Enum_One,
    Enum_Two
};

Special make_special( Enum e )
{
    switch( e )
    {
        case Enum_One:
            return Special( /*stuff one*/ );

        case Enum_Two:
            return Special( /*stuff two*/ );
    }
}

void do_enum( Enum e )
{
    switch( e )
    {
        case Enum_One:
            do_one();
            break;

        case Enum_Two:
            do_two();
            break;
    }
}
  • оставьте случай по умолчанию, gcc предупредит вас (будет визуальная студия?)
  • добавить случай по умолчанию с assert(false);
  • добавьте случай по умолчанию, который выдает исключение catchable
  • добавьте случай по умолчанию, который выдает исключение, исключающее исключение (это может быть просто политикой, чтобы никогда не поймать ее или не перевернуть).
  • что-то лучшее, что я не рассматривал

Мне особенно интересно, почему вы решили сделать это так, как вы.

4b9b3361

Ответ 1

Я делаю исключение. Так же, как яйца являются яйцами, кто-то передаст целое число с плохим значением, а не значением перечисления в ваш коммутатор, и лучше всего шуметь, но дать программе возможность выдавать ошибку, которая assert() не делает.

Ответ 2

Я бы поставил assert.

Special make_special( Enum e )
{
    switch( e )
    {
        case Enum_One:
            return Special( /*stuff one*/ );

        case Enum_Two:
            return Special( /*stuff two*/ );

        default:
            assert(0 && "Unhandled special enum constant!");
    }
}

Не обрабатывать значение перечисления, в то время как намерение охватить все случаи, это ошибка в коде, который должен быть исправлен. Ошибка не может быть разрешена и обработана "изящно", и ее необходимо сразу зафиксировать (поэтому я бы не выбрал). Для того, чтобы компилятор молчал о предупреждениях "возврат без значения", вызовите abort, например:

#ifndef NDEBUG
#define unreachable(MSG) \
  (assert(0 && MSG), abort())
#else
#define unreachable(MSG) \
  (std::fprintf(stderr, "UNREACHABLE executed at %s:%d\n", \
                __FILE__, __LINE__), abort())
#endif 

Special make_special( Enum e )
{
    switch( e )
    {
        case Enum_One:
            return Special( /*stuff one*/ );

        case Enum_Two:
            return Special( /*stuff two*/ );

        default:
            unreachable("Unhandled special enum constant!");
    }
}

Отсутствие предупреждения компилятором о возврате без значения, потому что он знает, что abort никогда не возвращается. Мы сразу же прекращаем неудачную программу, которая, по моему мнению, является единственной разумной реакцией (нет смысла пытаться продолжить выполнение программы, вызвавшей поведение undefined).

Ответ 3

Прежде всего, я бы всегда имел default в инструкции switch. Даже если нет идиотов вокруг бросить int egers на enum s, всегда есть вероятность повреждения памяти, которую default может уловить. Для чего это необходимо, правила MISRA делают требование по умолчанию обязательным.

Относительно того, что вы делаете, это зависит от ситуации. Если исключение можно обработать хорошим способом, обработайте его. Если это переменная состояния в некритической части кода, рассмотрите молчание сброса переменной состояния в исходное состояние и продолжения (возможно, для регистрации ошибки для будущей справки). Если это приведет к сбою всей программы в самом запутанном виде, попытайтесь грациозно упасть или что-то в этом роде. Короче говоря, все зависит от того, чем вы являетесь switch и насколько плохим может быть неправильное значение.

Ответ 4

В качестве дополнительного замечания (в дополнение к другим ответам) я хотел бы отметить, что даже на языке С++ с его относительно строгими ограничениями безопасности типов (по крайней мере, по сравнению с C) можно создать значение перечисления тип, который в общем случае может не соответствовать ни одному из счетчиков, без использования каких-либо "хаков".

Если у вас есть тип перечисления E, вы можете на законных основаниях сделать это

E e = E();

который инициализирует E нулевым значением. Это совершенно законно в С++, даже если декларация E не содержит константу перечисления, которая обозначает 0.

Другими словами, для любого типа перечисления E выражение E() хорошо сформировано и генерирует нулевое значение типа E, независимо от того, как определяется E.

Обратите внимание, что эта лазейка позволяет создать потенциально "неожиданное" значение перечисления без использования каких-либо "хаков", как приведение значения int к типу перечисления, указанному в вашем вопросе.

Ответ 5

Ваши вещи хороши. Но я удаляю "throw catchable exception".

Дополнительно:

  • Предоставлять предупреждения как ошибки.
  • Добавить регистрацию для случаев по умолчанию.

Ответ 6

утвердить, а затем, возможно, бросить.

Для внутреннего кода, который находится в том же проекте, что и этот (вы не сказали, что такое граница функции - внутренняя lib, внешняя библиотека, внутренний модуль,...), он будет утверждать во время dev. Это то, что вы хотите.

Если код предназначен для общественного потребления (другие команды, для продажи и т.д.), то утверждение исчезнет, ​​и вы останетесь с броском. Это более вежливо для внешних потребителей

Если код всегда внутренний, просто утвердите

Ответ 7

Мои $0,02:

Если этот метод внешне видимый (вызываемый кодом, который вы не контролируете), тогда вам, вероятно, придется обработать возможность отправки вами некорректного значения перечисления. В этом случае выведите исключение.

Если этот метод является внутренним по отношению к вашему коду (только вы его вызываете), тогда утверждение должно быть всем, что необходимо. Это поймает случай когда-нибудь добавит новое значение перечисления и забудет обновить оператор switch.

Всегда предоставляйте случай по умолчанию в каждом коммутаторе и, по крайней мере, утверждайте, если он попадает. Эта привычка будет окупаться, экономя ваши часы или даже дни царапин головы каждый раз.

Ответ 8

В дополнение к рекомендациям для исключения исключений, если вы используете gcc, вы можете использовать переключатель -Wswitch-enum (и в конечном итоге -Werror = -enum), который добавит предупреждение (или ошибку), если член вашего перечисления не появляется ни в каком случае. Для других компиляторов может быть эквивалент, но я использую его только для gcc.

Ответ 9

Я предпочитаю вариант 2:

добавить случай по умолчанию, который выдает исключение catchable

Это должно подчеркнуть проблему, если она когда-либо встречается, и она только обойдется вам в пару строк для imlpement.

Ответ 10

В качестве дополнительной опции: Избегайте переключения перечислений.

Ответ 11

В случаях, аналогичных приведенному примеру, вы можете комбинировать опцию 1 с одним из других вариантов: Опустите значение по умолчанию и включите соответствующее предупреждение компилятора, если оно доступно. Таким образом, вы сразу обнаруживаете, если добавлено новое перечисляемое значение, и вы можете удобно добавить этот случай, не гарантируя, что определенный путь кода будет выполнен, чтобы найти его во время выполнения.

Затем между концом коммутатора и концом функции добавьте код для утверждения или выброса (я предпочитаю утверждать, что это действительно неверная ситуация). Таким образом, если кто-то переводит int в ваш тип перечисления, вы все равно получаете проверку выполнения.

Ответ 12

Я не парень на C++. Но, в С#, я бы написал это как

enum Enum
{
    Enum_One,
    Enum_Two
};


Special make_special( Enum e )
{

    if(Enums.IsDefined(typeof(Enum),(int)e))
    {
       switch( e )
       {
          case Enum_One:
              return Special( /*stuff one*/ );
          case Enum_Two:
              return Special( /*stuff two*/ );
       }
    }
    // your own exception can come here.
    throw new ArgumentOutOfRangeException("Emum Value out of range");

}

Ответ 13

Я лично рекомендую любые ваши решения, кроме первого. Оставьте в случае по умолчанию и выполните команду throw (любой тип исключения, который вам нравится). В С# Visual Studio не предупреждает вас, если вы не учитываете случай по умолчанию.

Причина, по которой я предлагаю вам добавить случай и не выполнить его, заключается в том, что это важный момент обслуживания кода. Как только кто-то добавляет в список, оператор switch должен расти, чтобы соответствовать.

Также (как сказал UncleBen), посмотрите, можете ли вы создать дизайн для всего этого сценария, используя полиморфизм. Таким образом, когда вы добавляете новое значение в перечисление, вам нужно добавить его только в одном месте. Каждый раз, когда вы видите переключатель на перечислении, вы должны использовать вместо него полиморфизм.

Ответ 14

Помимо исключений, которые были бы предпочтительным решением во время выполнения (и будут обрабатывать сумасшедшие кастинга), я также стараюсь использовать утверждения статического компиляции.

Вы можете сделать следующее:

//this fails at compile time when the parameter to the template is false at compile time, Boost should provide a similar facility
template <COLboolean IsFalse> struct STATIC_ASSERTION_FAILURE;
template <> struct STATIC_ASSERTION_FAILURE<true>{};
#define STATIC_ASSERT( CONDITION_ ) sizeof( STATIC_ASSERTION_FAILURE< (COLboolean)(CONDITION_) > );

//
// this define will break at compile time at locations where a switch is being
// made for the enum. This helps when adding enums
//
// the max number here should be updated when a new enum is added.
//
// When a new enum is added, at the point of the switch (where this
// define is being used), update the switch statement, then update
// the value being passed into the define.
//
#define ENUM_SWITCH_ASSERT( _MAX_VALUE )\
   STATIC_ASSERT( _MAX_VALUE  ==  Enum_Two)

enum Enum
{
    Enum_One = 0,
    Enum_Two = 1
};

Затем в вашем коде всякий раз, когда вы используете набор перечислений:

ENUM_SWITCH_ASSERT( Enum_Two )
switch( e )
{
    case Enum_One:
        do_one();
        break;
    case Enum_Two:
        do_two();
        break;
}

Теперь, когда вы меняете макрос ENUM_SWITCH_ASSERT для обработки нового значения enum, он будет разбиваться во время компиляции рядом с местоположениями, в которых используется набор перечислений. Помогает при добавлении новых случаев.

Ответ 15

Поскольку это может помочь узнать, что такое неожиданное значение перечисления, напишите свой собственный макрос BADENUM (Enum) в строках:

#define _STRIZE(x) _VAL(x)
#define _VAL(x) #x
extern void  __attribute__ ((noreturn)) AssertFail(const char *Message);
#define ASSERT(Test) ((Test) ? (void)0 : AssertFail(__FILE__ ":" _STRIZE(__LINE__) " " #Test))

extern void  __attribute__ ((noreturn)) BadEnum(const char *Message, const long unsigned Enum);
#define BADENUM(Enum)  BadEnum(__FILE__ ":" _STRIZE(__LINE__), (u32)Enum))

Ответ 16

Мнение стандартов кодирования LLVM: Не используйте метки по умолчанию в полностью закрытых переключателях по перечислениям

Их обоснование таково:

-Wswitch предупреждает, что переключатель, без метки по умолчанию, над перечислением не охватывает каждое значение перечисления. Если вы пишете метку по умолчанию на полностью закрытом коммутаторе по перечислению, тогда предупреждение -Wswitch не будет срабатывать, когда к этому перечислению будут добавлены новые элементы. Чтобы избежать добавления этих значений по умолчанию, Clang имеет предупреждение -Wcovered-switch-default, которое отключено по умолчанию, но включено при создании LLVM с версией Clang, которая поддерживает это предупреждение.

Основываясь на этом, мне лично нравится:

enum MyEnum { A, B, C };

int func( MyEnum val )
{
    boost::optional<int> result;  // Or `std::optional` from Library Fundamental TS

    switch( val )
    {
    case A: result = 10; break;
    case B: result = 20; break;
    case C: result = 30; break;
    case D: result = 40; break;
    }

    assert( result );  // May be a `throw` or any other error handling of choice

    ...  // Use `result` as seen fit

    return result;
}

Если вы выбираете return для каждого случая коммутатора, вам не нужно boost::optional: просто вызовите std::abort() или throw безоговорочно сразу после блока switch.

Тем не менее важно помнить, что, возможно, switch не лучший инструмент для разработки (как уже сказано в @UncleBens answer): Полиморфизм или какой-то тип таблицы lookup-table может найти лучшее решение, особенно если в вашем перечислении много элементов.

PS: Как любопытство, Руководство по стилю Google С++ делает исключение для switch s-over- enum не для имеют default случаи:

Если не указано условное значение, операторы switch всегда должны иметь случай по умолчанию