Использование default в операторе switch при переключении перечисления - PullRequest
33 голосов
/ 04 февраля 2010

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

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

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 предупредит вас (будет Visual Studio?)
  • добавить регистр по умолчанию с assert(false);
  • добавить регистр по умолчанию, который генерирует ловимое исключение
  • добавить регистр по умолчанию, который генерирует неуловимое исключение (это может быть просто политика никогда не перехватывать его или всегда перебрасывать).
  • что-то лучшее, что я не рассматривал

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

Ответы [ 16 ]

30 голосов
/ 04 февраля 2010

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

23 голосов
/ 04 февраля 2010

Я бы поставил 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 никогда не вернется. Мы немедленно завершаем работу программы, в которой произошел сбой, что, на мой взгляд, является единственной разумной реакцией (нет смысла продолжать запускать программу, которая вызвала неопределенное поведение).

7 голосов
/ 04 февраля 2010

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

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

6 голосов
/ 04 февраля 2010

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

Если у вас есть тип enum E, вы можете легально сделать это

E e = E();

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

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

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

2 голосов
/ 04 февраля 2010

Ваши предметы хороши. Но я бы удалил «бросаемое исключение».

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

  • Сделать предупреждения, которые будут рассматриваться как ошибки.
  • Добавить ведение журнала для случаев по умолчанию.
1 голос
/ 18 августа 2015

Стандарты кодирования 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 ): Полиморфизм или некоторый тип справочной таблицы могли бы лучше решение, особенно если в вашем перечислении много элементов.

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

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

1 голос
/ 06 декабря 2010

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

1 голос
/ 04 февраля 2010

Мои $ 0,02:

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

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

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

1 голос
/ 04 февраля 2010

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

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

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

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

1 голос
/ 04 февраля 2010

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

...