Использование 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 ]

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

Я склонен сделать вариант 2:

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

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

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

Поскольку это может помочь узнать, какое неожиданное значение было в enum, напишите свой собственный макрос 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))
0 голосов
/ 04 февраля 2010

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

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

//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 голосов
/ 04 февраля 2010

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

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

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

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

Я не парень С ++. Но в 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 голосов
/ 04 февраля 2010

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

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

...