对于嵌入式项目中的 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++ 禁止对‘枚举’类型的前向引用”和“不能在类型说明符中定义匿名枚举”...
是否有另一种方法可以使这个想法起作用(模板化一个带有枚举的类),或者这行不通?
答案 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) ) ;
如果实际上这些枚举只是导致调用不同的菜单项处理函数,那么您可以改为使用指向函数的指针数组,而不是枚举/开关或其他任何东西。