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

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

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

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

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

33
задан deft_code 4 February 2010 в 16:51
поделиться

13 ответов

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

30
ответ дан 27 November 2019 в 17:55
поделиться

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

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

//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 для обработки нового значения перечисления, он будет ломаться во время компиляции рядом с местами, которые используйте набор перечислений. Очень помогает при добавлении новых дел.

0
ответ дан 27 November 2019 в 17:55
поделиться

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

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

  • Сделать предупреждения, которые будут рассматриваться как ошибки.
  • Добавить регистрацию для случаев по умолчанию.
2
ответ дан 27 November 2019 в 17:55
поделиться

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

2
ответ дан 27 November 2019 в 17:55
поделиться

Искусство геймдизайна - книга объективов Джесси Шелла

Джесси Шелл преподавал геймдизайн и руководил исследовательскими проектами в Центре развлекательных технологий Карнеги Меллона с 2002 года.

- сказал Нуфф.

Искусство игрового дизайна - Книга линз http://i50.tinypic.com/iekw0l.jpg

PS: Извините, если я дважды пишу, я не смог найти эту книгу в ответах - либо потому, что название было неточным, либо не было изображения. Дайте мне знать, и я удалю его, если да.

-121--1750359-

Вот все, что я могу думать о верхней части головы:

  1. main () возвращает (значение или void () основной заканчивает выполнение последней инструкции)

  2. программа выбрасывает исключение uncaught

  3. System.exit (int)

  4. Это может произойти сбой?

-121--4817683-

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

При наличии типа перечисления E это

E e = E();

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

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

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

7
ответ дан 27 November 2019 в 17:55
поделиться

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

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

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

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

1
ответ дан 27 November 2019 в 17:55
поделиться

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

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

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

0
ответ дан 27 November 2019 в 17:55
поделиться

Я не С++. Но, на 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");

}
0
ответ дан 27 November 2019 в 17:55
поделиться

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

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

0
ответ дан 27 November 2019 в 17:55
поделиться

Мои $ 0,02:

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

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

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

1
ответ дан 27 November 2019 в 17:55
поделиться

Я предпочитаю использовать вариант 2:

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

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

1
ответ дан 27 November 2019 в 17:55
поделиться

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

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

7
ответ дан 27 November 2019 в 17:55
поделиться

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

23
ответ дан 27 November 2019 в 17:55
поделиться
Другие вопросы по тегам:

Похожие вопросы: