循环遍历枚举的好方法是什么?

时间:2021-07-21 19:30:18

标签: c++ embedded

背景

对于嵌入式项目中的 UI,我正在寻找一种很好的通用方式来存储“状态”并通过按下按钮来循环它,例如菜单项列表。

通常,我喜欢为此使用枚举,例如:

enum class MenuItem {
    main,
    config,
    foo,
    bar,
};

然后,在我的 UI 代码中,我可以存储一个 currentMenuItem 状态,如

MenuItem currentMenuItem = MenuItem::MAIN;

并通过将 currentMenuItem 与其在枚举中声明的任何可能值进行比较来根据当前状态执行操作。

问题

问题是,我现在想进入下一个菜单项。为此,我可以非常轻松地编写一个函数,通过强制转换为 int,递增 1,然后将其转换回枚举来执行此操作。我有多个不同的枚举,所以我什至可以使用这个模板化函数来为任意枚举执行此操作:

template <typename T>
void advanceEnum(T &e) {
    e = static_cast<T>(static_cast<int>(e) + 1);
}

这样做的问题是它没有回绕:它会很高兴地继续增加底层整数,使其超出实际枚举中的元素数量。我可以很容易地解决这个问题(通过对上述函数中的元素数取模),只要有一种干净的方法来获取枚举的元素数。据我所知,实际上没有。

自定义枚举?

我正在考虑编写一个实现此行为的自定义“CyclicEnum”类,我随后可以从中派生。这样,我也可以把它写成一个重载的 operator++

然而,我还没有想出如何在不实际使用枚举的情况下获得类似枚举的东西。例如,我得到了这样的东西:

class CyclicEnum {
public:
    uint8_t v;

    CyclicEnum& operator++() {
        v = (v+1) % count;
        return *this;
    }

    CyclicEnum operator++(int) {
        CyclicEnum old = *this;
        operator++();
        return old;
    }
private:
    uint8_t count;

protected:
    CyclicEnum(uint8_t v, uint8_t count) : v(v), count(count) {}
};

struct Tab : public CyclicEnum {
    enum Value {
        main,
        config,
        foo,
        bar,
    };

    Tab(Value v) : CyclicEnum(v, 4) {}
};

但是,正如您所看到的,这仍然在自定义 CyclicEnum 类中使用了一个枚举,而且我又回到了同样的问题:我无法计算 Enum 元素的数量,所以我必须手动指定(我认为这不好,因为它是多余的)。其次,通过这种方式,我还必须覆盖派生类中的构造函数,我希望避免它尽可能保持干净。

在搜索此问题时,许多人显然建议在枚举末尾添加一个“虚拟”值作为获取大小的技巧:

enum Value {
    main,
    config,
    foo,
    bar,
    _count,
};

但坦率地说,我觉得这很难看,因为 _count 现在实际上是一个有效的选项。

有什么办法可以解决这个问题吗?我在滥用枚举吗?看看枚举显然(按设计)如此难以计数的事实,可能。但是,拥有像枚举提供的命名值这样的结构的好方法是什么?

编辑:

好的,我确信在末尾使用 _count 元素并不是一个坏主意。不过,我想将其封装在某种结构中,例如类。

我也想过不使用继承,而是使用类模板来实现这一点,就像这样:

(注意,这不会编译):

template<typename T>
struct CyclicEnum {
    T v;
    enum Values = T;

    CyclicEnum& operator++() {
        v = (v+1) % T::_count;
        return *this;
    }

    CyclicEnum operator++(int) {
        CyclicEnum old = *this;
        operator++();
        return old;
    }
    
    CyclicEnum(T v) : v(v) {}
};

struct MenuItem : public CyclicEnum<enum class {
    main,
    config,
    foo,
    bar,
    _count,
}> {};

但是,这不起作用,因为“ISO C++ 禁止对‘枚举’类型的前向引用”和“不能在类型说明符中定义匿名枚举”...

是否有另一种方法可以使这个想法起作用(模板化一个带有枚举的类),或者这行不通?

3 个答案:

答案 0 :(得分:4)

<块引用>

问题是,我现在想进入下一个菜单项。

++ 浮现在脑海中。保持简单。

<块引用>

那样,我也可以把它写成一个重载的运算符++

是的...或者再一次,保持简单,你可以放弃整个课程。没有必要围绕一个简单的整数编写抽象层。真的。

<块引用>

在搜索这个问题时,许多人显然建议在枚举末尾添加一个“虚拟”值作为获取大小的技巧

当然为什么不呢。这是非常普遍的做法。

<块引用>

但坦率地说,我觉得这很丑陋,因为 LAST 现在实际上是一个有效的选项。

这是规范代码,并不难看。只需给它一个合理的名称,也许像 MENU_ITEMS_N 这样的东西来建议这是一个计数器变量,然后就这样使用它。 for(int i=0; i<MENU_ITEMS_N; i++) ...

<块引用>

我在滥用枚举吗?

枚举只是命名的整数值。不要过度设计你的代码。这对性能不利,对维护不利,增加了不必要的复杂性。

答案 1 :(得分:3)

您可以使用 magic_enum 库来反映枚举。
获取所有枚举元素的名称作为 std::array < std::string_view > 并打印它们的示例。

#include <algorithm>
#include <iostream>
#include <magic_enum.hpp>

enum struct Apple
{
  Fuji = 2,
  Honeycrisp = -3,
  Envy = 4
};

int
main ()
{
  constexpr auto &appleNames = magic_enum::enum_names<Apple> ();                                                 // get an std::array<std::string_view> with the names for the enum sorted by value
  std::copy (appleNames.begin (), appleNames.end (), std::ostream_iterator<std::string_view> (std::cout, "\n")); // print all the names
}

印刷品:
蜜脆
富士
羡慕

有一些限制,请阅读magic enum limitations

答案 2 :(得分:1)

而不是使用 _count,将最后一个“哨兵”值设置为最后一个实际值。

enum Value {
    main,
    config,
    foo,
    bar,
    last = bar
};

这样就可以避免 enum 值不是有效菜单选项的问题。例如,在您的增量中而不是:

v = static_cast<Value>( (static_cast<int>(v) + 1) % 
                        static_cast<int>(Value::_count) );

你会:

v = static_cast<Value>( (static_cast<int>(v) + 1) %
                        (static_cast<int>(Value::last) + 1) ) ;

如果实际上这些枚举只是导致调用不同的菜单项处理函数,那么您可以改为使用指向函数的指针数组,而不是枚举/开关或其他任何东西。