2015-02-19 3 views
1

Я хочу сопоставить значения (перечислимого) перечисления с некоторыми другими значениями. Например, здесь я карта Color к другому перечислению Group:Перечисление enum устойчивое к рефакторингу

enum class Color { 
    Red, Green, Blue, Cyan, Magenta, Yellow, White, Black, 
    COUNT // Needed to know the number of items 
}; 
enum class Group { 
    Primary, Secondary, Neutral 
}; 

Group GetGroupOfColor(Color color); // Mapping function I'm going to implement 

Я хочу, чтобы убедиться, что если кто-то изменяет количество элементов в Color перечисления, то эта функция не будет компилироваться.

я придумал единственное решение этой проблемы:

Group GetGroupOfColor(Color color) 
{ 
    static const Group color2group[] = { 
    Group::Primary, // Red 
    Group::Primary, // Green 
    Group::Primary, // Blue 
    Group::Secondary, // Cyan 
    Group::Secondary, // Magenta 
    Group::Secondary, // Yellow 
    Group::Neutral, // White 
    Group::Neutral  // Black 
    }; 
    static_assert(ARRAY_SIZE(color2group) == size_t(Color::COUNT), "DEADBEEF!"); 

    auto index = size_t(color); 
    assert(index < size_t(Color::COUNT)); 
    return color2group[index]; 
} 

где ARRAY_SIZE могут быть реализованы как следующие:

template <typename T, size_t N> 
constexpr size_t ARRAY_SIZE(T(&)[N]) 
{ 
    return N; 
} 

Эта реализация делает то, что я хочу, но у него есть куча недостатков:

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

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

Смотрите также:

+0

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

ответ

2

Я хотел бы использовать switch заявление.

switch (colour) { 
    case Colour::Red: return Group::Primary; 
    //... 
    case Colour::Black: return Group::Neutral; 
} 
return Group::Invalid; // or throw, assert, or whatever. 

Это должно охватывать все ваши потребности:

Добавляет этот уродливый COUNT пункт в Color enum (не беспокоит меня больше)

Нет необходимости в том, что, только case для каждого переписчика.

потерпит неудачу тихо, если кто-то сортирует предметы Color

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

Не применимо для не непрерывных перечислений

Снова, названный case заявления не заботятся о фактических значениях.

если кто-то изменяет количество элементов в Color enum, эта функция не будет компилироваться

Хотя не гарантируется, большинство компиляторов должны быть в состоянии предупредить, если есть необработанные счетчики в переключателе (до тех пор, так как нет ветки default). Для GCC опция -Wswitch (включена в -Wall), а также -Werror, если вы хотите, чтобы она вышла из строя.

+0

Очень хорошая мысль о предупреждении компилятора! – Mikhail

1

Как насчет немного другой подход - создать своего рода обогащенным перечисления:

class Color { 
    public : 
    enum class Name { RED, GREEN, BLUE, CYAN, MAGENTA, YELLOW, WHITE, BLACK }; 
    enum class Group { PRIMARY, SECONDARY, NEUTRAL }; 

    uint32_t rgb; 
    Name name; 
    Group group; 

    static const Color Red; 
    static const Color Green; 
    static const Color Blue; 
    static const Color Cyan; 
    static const Color Magenta; 
    static const Color Yellow; 
    static const Color White; 
    static const Color Black; 

    private : 
    Color(uint32_t rgb, Name name, Group group) : rgb(rgb), name(name), group(group) { } 

    public : 
    inline operator const Name() const { return name; } 
}; 

const Color Color::Red  = Color(0xFF0000, Color::Name::RED,  Color::Group::PRIMARY); 
const Color Color::Green = Color(0x00FF00, Color::Name::GREEN, Color::Group::PRIMARY); 
const Color Color::Blue = Color(0x0000FF, Color::Name::BLUE, Color::Group::PRIMARY); 
const Color Color::Cyan = Color(0x00FFFF, Color::Name::CYAN, Color::Group::SECONDARY); 
const Color Color::Magenta = Color(0xFF00FF, Color::Name::MAGENTA, Color::Group::SECONDARY); 
const Color Color::Yellow = Color(0xFFFF00, Color::Name::YELLOW, Color::Group::SECONDARY); 
const Color Color::White = Color(0xFFFFFF, Color::Name::WHITE, Color::Group::NEUTRAL); 
const Color Color::Black = Color(0x000000, Color::Name::BLACK, Color::Group::NEUTRAL); 

, который затем может быть использован, как это:

void fun(const Color& color) { 
    switch (color) { 
     case Color::Name::RED : std::cout << "red"; break; 
     case Color::Name::GREEN : std::cout << "green"; break; 
     case Color::Name::BLUE : std::cout << "blue"; break; 
     // etc. 
    } 
    std::cout << " "; 
    switch (color.group) { 
     case Color::Group::PRIMARY : std::cout << "primary"; break; 
     case Color::Group::SECONDARY : std::cout << "secondary"; break; 
     case Color::Group::NEUTRAL : std::cout << "neutral"; break; 
    } 
    std::cout << " : " << std::hex << color.rgb << std::endl; 
} 
+0

Интересный подход. Применимость в значительной степени зависит от вариантов использования. Нужно подумать об этом. Благодарю. – Mikhail

1

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

#define COLORMAP \ 
    MAP_COLOR(Red, Primary) \ 
    MAP_COLOR(Green, Primary) \ 
    MAP_COLOR(Blue, Primary) \ 
    MAP_COLOR(Cyan, Secondary) \ 
    MAP_COLOR(Magenta, Secondary) \ 
    MAP_COLOR(Yellow, Secondary) \ 
    MAP_COLOR(White, Neutral) \ 
    MAP_COLOR(Black, Neutral) 

Определение MAP_COLOR надлежащим образом тогда позволит вам определить Color перечисление, а также функции отображения из одного источника:

#define MAP_COLOR(a, b) a, 
enum class Color { 
    COLORMAP 
    Invalid 
}; 
#undef MAP_COLOR 

enum class Group { 
    Primary, Secondary, Neutral, Invalid 
}; 

#define MAP_COLOR(a, b) case Color::a: return Group::b; 
Group GetGroupOfColor(Color color) 
{ 
    switch (color) { 
    COLORMAP 
    case Color::Invalid: return Group::Invalid; 
    } 
    return Group::Invalid; 
} 
+0

Я считаю, что это называется техникой «X Макро». Это интересно, но очень неясно. Он также связывает определение элементов 'Color' с элементами' Group'. Что делать, если я хочу отображать цвета на других объектах или вообще избавляться от 'Group'? – Mikhail

+0

@Mikhail: если вы хотите сопоставить цвета с другими значениями, вы должны расширить макрос 'MAP_COLOR', чтобы принять три (или даже больше) аргумента. –

+0

@Mikhail: Кстати, спасибо за упоминание термина «X macro», я даже не знал, что у этой техники было имя! –

Смежные вопросы