【问题标题】:Checking total order on C++11 enum class (how to implement operator<)检查 C++11 枚举类的总顺序(如何实现 operator<)
【发布时间】:2016-08-04 22:46:41
【问题描述】:

在编写简单的状态机时,您通常有一个总顺序,例如:

State {
  initial,
  connecting,
  established,
  ready,
  closed,
  failed
}

您可以轻松测试给定值的等价性:

if (state == State::established)

但是如果你的状态是ready——这个测试会失败,即使准备好意味着建立?另一种方法是添加另一个子句,但如果列表更长,或者要求不断变化,这可能会变得更长。

if (state == State::established || state == State::ready || ...)

使用 C 风格的枚举,您甚至可以通过转换为整数来确定相对顺序:

if (state > State::established && state <= State::closed)

但是如果你想享受类型安全枚举类,除了添加 operator

【问题讨论】:

  • 欢迎来到 StackOverflow。您遇到的实际问题是什么?请提供Minimal, Complete, and Verifiable example 并更详细地解释问题。
  • 您是在问如何为 C++11 范围的枚举实现 operator&lt;operator&gt;
  • 是的!对不起,如果原来的措辞很差。第一个计时器。
  • 您可以考虑将该措辞添加到问题中。我认为它非常清晰和直接。
  • 您最好的选择可能是使用位掩码或创建一个专用的 State 类来处理这个问题,而不是试图将作用域枚举破解成它不是的东西。无论如何,对枚举使用数值比较可能是一种非常糟糕的形式,因为枚举旨在唯一地标识一个命名值,仅此而已。

标签: c++ class enums state


【解决方案1】:

如您所知,作用域枚举提供类型安全性,因为它们不能隐式转换为整数。

但没有什么能阻止您将枚举转换为整数:

if ((int)state > (int)State::established && (int)state <= (int)State::closed)

您可以使用 static_cast 的 C 风格转换。

【讨论】:

    【解决方案2】:

    我倾向于将我需要的运算符定义为内联函数,并根据需要使用 static_cast 来获取要比较的值:

    enum class MyEnum : unsigned { ALPHA, BETA, DELTA, EPSILON };
    inline bool operator<(MyEnum a, MyEnum b)  { return static_cast<unsigned>(a) < static_cast<unsigned>(b); }
    inline bool operator>(MyEnum a, MyEnum b)  { return static_cast<unsigned>(a) > static_cast<unsigned>(b); }
    inline bool operator<=(MyEnum a, MyEnum b) { return static_cast<unsigned>(a) <= static_cast<unsigned>(b); }
    inline bool operator>=(MyEnum a, MyEnum b) { return static_cast<unsigned>(a) >= static_cast<unsigned>(b); }
    

    (注意:在 C++20 中,这会变得更简单,因为您可以只定义一个运算符“”运算符,然后自动生成其他运算符。)

    如果我需要做更复杂的技巧,我可以使用静态 std::unordered_map 将值(或值对)映射到我需要的任何东西:

    #include <unordered_map>
    
    const std::string& getName(MyEnum e)
    {
       static const std::string UNKNOWN = "???";
       static const std::unordered_map<MyEnum, std::string> NAMES = {
          {MyEnum::ALPHA,   "ALPHA"},
          {MyEnum::BETA,    "BETA"},
          {MyEnum::DELTA,   "DELTA"},
          {MyEnum::EPSILON, "EPSILON"},
       };
       const auto it = NAMES.find(e);
       return ((it != NAMES.end()) ? it->second : UNKNOWN);
    }
    

    【讨论】:

      【解决方案3】:

      有人可能会说这是一个有缺陷的请求,因为枚举值的总顺序是实现方法的历史事故,或者状态机很少是线性的(失败意味着关闭 em>,但失败并不一定意味着连接已经准备好)。

      但是,我发现它出现的频率足以找到解决方案,直到您转向更复杂的东西(或者您的状态机增长)。我想出了以下不需要将枚举转换为整数值的解决方案。

      bool has_reached(State current, State condition)
      {
          bool reached = false;
          switch (condition)
          {
          case State::initial:     reached |= current == State::initial;
          case State::connecting:  reached |= current == State::connecting;
          case State::established: reached |= current == State::established;
          case State::ready:       reached |= current == State::ready;
          case State::closed:      reached |= current == State::closed;
          case State::failed:      reached |= current == State::failed;
          };
          return reached;
      }
      

      按如下方式使用:

      if (has_reached(state, State::established)) // State::ready returns true
      

      它在现代系统上应该很快并且很难出错——只要你不混淆声明和切换命令。

      【讨论】:

      • “我想出了以下不需要转换为整数的解决方案” - ...大声笑。真的吗?谁告诉你的?
      • @WhiZTiM:“真的”什么?
      • @Ryan:主观,但我更喜欢省略default 案例并注意警告,而不是在五个月后突然随机触发错误时对assert 感到惊讶。可惜没有static_assert 等价物。呵呵,想想也是个不错的提议..
      • @LightnessRacesinOrbit ... Ryan 说他的解决方案不需要转换为整数... 枚举通常是 treated ints
      • 在进入默认情况之前,您需要break。虽然我个人更喜欢简单地转换为整数而不是所有这些。
      猜你喜欢
      • 2012-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-01
      • 2020-07-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多