【发布时间】:2021-07-21 19:30:18
【问题描述】:
背景
对于嵌入式项目中的 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++ 禁止前向引用‘枚举’类型”和“不能在类型说明符中定义匿名枚举”...
是否有另一种方法可以使这个想法发挥作用(使用枚举模板类),或者这不起作用?
【问题讨论】:
-
C++ 的反射能力相当弱。一般来说,如果你想要一个可数的枚举,你会确保它们有连续的值,从 0 开始,并有一个最终的“计数器”枚举值,为此目的清楚地标记了它。类似“count_do_not_use”的东西。有时它会干扰关于
switch语句的编译器警告。这不是一个很好的模式,但它已经广为人知,并且是我们目前拥有的最好的模式。或者,您可以定义所有值的集合。前任。constexpr Value known_values[] = { /* all the enums*/ };但这需要重复所有枚举。 -
拥有
LAST(通常称为count)是个好主意。 “LAST 现在实际上是一个有效的选项” 是的,那又怎样?无论如何,您都可以执行Value(42)之类的操作。附带说明一下,我会保留将UPPERCASE_NAMES用于宏。 -
" 我觉得这很难看,因为 LAST 现在实际上是一个有效的选项。"这实际上是我所知道的最干净的解决方案。考虑基础类型的任何值都是“有效选项”。
MAIN、CONFIG、FOO等是一些命名常量,但基础类型的任何其他值都是枚举的有效值 -
advanceEnum通常称为operator++。 -
在你的枚举声明中使用
LAST=BAR让它更好