枚举映射对重构具有鲁棒性

Enum mapping robust to refactoring

本文关键字:鲁棒性 重构 映射 枚举      更新时间:2023-10-16

我想将(作用域(枚举的值映射到其他一些值。例如,在这里我将Color映射到其他枚举Group

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

我想确保如果有人更改 enum 中的元素数量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项目,将静默失败
  • 不适用于不连续的枚举,即具有显式分配的值(这个并不重要(

我的问题是,有没有办法改进这种实现?也许我什至没有想到一些不同的方法。也许有它自己的缺点,我会觉得不那么烦人。

另请参阅:

  • 枚举中的元素数
  • 不使用宏进行编译时sizeof_array

我会使用switch语句。

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

这应该满足您的所有需求:

Color enum中添加这个丑陋的COUNT项目(最困扰我(

不需要这个,只需为每个枚举器提供一个case

如果有人重新订购Color项目,将静默失败

每个case都显式命名,因此每个枚举器的值无关紧要(只要它们是唯一的;但如果不是这种情况,则会收到错误(。

不适用于不连续枚举

同样,命名case语句不关心实际值。

如果有人更改了Color enum中的元素数量,此函数将无法编译

虽然不能保证,但大多数编译器应该能够警告开关中是否存在未经处理的枚举器(只要没有default分支(。对于 GCC,该选项是 -Wswitch (包含在 -Wall 中(,如果您希望它导致失败,则-Werror

稍微不同的方法 - 创建一种丰富的枚举:

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;
}

我怀疑您想检测对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;
}