Обнаружение, если приведение int к перечислению приводит к неперечисляемому значению - PullRequest
12 голосов
/ 25 января 2011

Допустим, у меня есть что-то вроде этого:

enum CardColor { HEARTS, DIAMONDS, CLUBS, SPADES};

CardColor MyColor = static_cast<CardColor>(100);

Есть ли (простой) способ обнаружить во время компиляции или во время выполнения, что значение MyColor не соответствует ни одному из перечисленных значений?

И вообще, если значения перечисления не следуют друг за другом, например:

enum CardColor { HEARTS = 0, DIAMONDS, CLUBS = 4, SPADES};

Ответы [ 6 ]

15 голосов
/ 26 января 2011

CashCow представляет достойный ответ на этот вопрос: написать собственную функцию для выполнения проверенного приведения, безусловно, просто.

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

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

#include <stdexcept>
#include <boost/preprocessor.hpp>

// Internal helper to provide partial specialization for checked_enum_cast
template <typename Target, typename Source>
struct checked_enum_cast_impl;

// Exception thrown by checked_enum_cast on cast failure
struct invalid_enum_cast : std::out_of_range 
{ 
    invalid_enum_cast(const char* s)
        : std::out_of_range(s) { }
};

// Checked cast function
template <typename Target, typename Source>
Target checked_enum_cast(Source s)
{
    return checked_enum_cast_impl<Target, Source>::do_cast(s);
}

// Internal helper to help declare case labels in the checked cast function
#define X_DEFINE_SAFE_CAST_CASE(r, data, elem) case elem:

// Macro to define an enum with a checked cast function.  name is the name of 
// the enumeration to be defined and enumerators is the preprocessing sequence
// of enumerators to be defined.  See the usage example below.
#define DEFINE_SAFE_CAST_ENUM(name, enumerators)                           \
    enum name                                                              \
    {                                                                      \
        BOOST_PP_SEQ_ENUM(enumerators)                                     \
    };                                                                     \
                                                                           \
    template <typename Source>                                             \
    struct checked_enum_cast_impl<name, Source>                            \
    {                                                                      \
        static name do_cast(Source s)                                      \
        {                                                                  \
            switch (s)                                                     \
            {                                                              \
            BOOST_PP_SEQ_FOR_EACH(X_DEFINE_SAFE_CAST_CASE, 0, enumerators) \
                return static_cast<name>(s);                               \
            default:                                                       \
                throw invalid_enum_cast(BOOST_PP_STRINGIZE(name));         \
            }                                                              \
            return name();                                                 \
        }                                                                  \
    };

Вот как вы могли бы использовать это с вашим примером CardColor:

DEFINE_SAFE_CAST_ENUM(CardColor, (HEARTS) (CLUBS) (SPADES) (DIAMONDS))

int main()
{
    checked_enum_cast<CardColor>(1);   // ok
    checked_enum_cast<CardColor>(400); // o noez!  an exception!
}

Первая строка заменяет ваше определение enum CardColor ...; он определяет перечисление и предоставляет специализацию, которая позволяет использовать checked_enum_cast для приведения целых чисел к CardColor.

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

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

9 голосов
/ 25 января 2011

Самое простое решение во время выполнения - не использовать static_cast, а использовать функцию, которая выполняет проверку за вас. Если вы поместите свой enum в класс, вы можете сделать это через класс. Что-то вроде:

class CardCheck
{
public:
  enum CardColor { HEARTS, DIAMONDS, CLUBS, SPADES };

  explicit CardCheck( int x ) : c( static_cast< CardColor >( x ) )
  {
     switch( c )
     {
       case HEARTS: case DIAMONDS: case CLUBS: case SPADES:
          break;

       default:
         // assert or throw
    }
  }

  CardColor get() const
  {
     return c;
  }

 private:
  CardColor c;      
};
4 голосов
/ 17 февраля 2013

clang поддерживают динамические проверки переполнения. См. Ключ -fsanitize = enum . Программа, скомпилированная с этим переключателем, сообщит об ошибках присваивания перечисления через вывод stderr Это позволит вам делать отладочные тесты. Не подходит для проверки подозрительных данных в официальной сборке.

3 голосов
/ 25 января 2011

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

enum CardColor { HEARTS, DIAMONDS, CLUBS, SPADES, CARDS_COUNT};

CardColor MyColor = static_cast<CardColor>(100);

if (MyColor >= CARDS_COUNT) {
    /* Invalid value */
}
1 голос
/ 25 января 2011

В начале - это плохая идея.

Но если вы хотите, я предлагаю жестко закодировать целочисленные значения перечисления:

enum CardColor { HEARTS = 10, DIAMONDS = 11, CLUBS = 12, SPADES = 13};

Затем оператор перегрузки:

CardColor operator = (int value)
{
    switch (value)
    {
        case 10:
            return HEARTS;
        // case for other values
        default:
            // throw an exception or something
    }
}
0 голосов
/ 25 января 2011

Значения перечисления могут перекрываться или иметь дыры;кроме того, фактическим переменным можно присвоить ноль, любое значение из набора или побитовое ИЛИ допустимых значений.Итак:

enum suites { hearts, diamonds, clubs, spades };

допускает значения 0, 1, 2, 3;

enum suites { hearts = 1 << 0, diamonds = 1 << 1, clubs = 1 << 2, spades = 1 << 4 };

допускает любое значение от 0 до 15.

Если вы используете enumдля определения битовых значений обычно рекомендуется определить (двоичные) operator&, operator|, operator&= и operator|=.Если вы этого не сделаете, вам потребуется явное приведение всякий раз, когда генерируется значение, не входящее в набор, поэтому места, где это происходит, можно легко обнаружить.

Существуют компиляторы, которые могут предупредить, еслиприсваивается номер за пределами допустимого диапазона или, если не указано ни одно, к первому или всем именам прикреплены инициализаторы (это является нарушением правил MISRA-C ).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...