考虑一个C ++类,它导出一个枚举,在该枚举上维护一个内部数组,并希望导出一个接受枚举值的命令。
class foo {
public:
enum color {
red,
yellow,
green,
NUM_COLORS
};
private:
something somebody[NUM_COLORS];
public:
void command(color c);
};
是否有一种干净的方法只能导出实际颜色,而不是NUM_COLORS?当编译器的类型系统真的应该能够为我做这件事时,我不想在每次调用时检查边缘情况。
明显的黑客是:
class foo {
public:
enum color {
red,
yellow,
green
};
private:
/* something like */ const unsigned NUM_COLORS = green+1;
unsigned LEDs_in_stock[NUM_COLORS];
public:
void command(color c);
};
这当然是一个滴答作响的定时炸弹,等待一些可怜的过度工作的维护程序员添加蓝色LED的配置,并忘记更新NUM_COLORS行。
让我澄清一下。在这种特殊情况下,我想要的是能够说:
class foo {
public:
enum color {
red,
yellow,
green
};
void command(color c);
private:
something somebody[color];
};
据我所知,C ++不允许这样做。
答案 0 :(得分:3)
将枚举放入基类中是一个选项吗?
class foo_enums {
public:
enum color {
red,
yellow,
green,
NUM_COLORS
};
protected:
foo_enums() { }
~foo_enums() { }
};
class foo : public foo_enums {
private:
unsigned LEDs_in_stock[NUM_COLORS];
/* make NUM_* values inaccessible */
using foo_enums::NUM_COLORS;
public:
void command(color c);
};
我个人不会这样做,因为它看起来像是一个过于复杂的工作。我只是禁止来电者通过NUM_COLORS
。没错,类型系统不会检查。但对于检查人类程序员来说,这肯定是一件容易的事情。他们为什么会通过NUM_COLORS
?
答案 1 :(得分:2)
我的第一个想法是尝试解决问题,但在经过一番反思后,我会将负担转移到command
:
void command(color c) {
assert(0 <= c && c < NUM_COLORS && "Invalid argument");
}
由于枚举是如此弱的类型,你需要检查输入,因为任何人都可以轻松提供蹩脚的参数:
Foo foo;
foo.command(static_cast<Foo::color>(3)); // 3 is green, right ?
原始解决方案:
class Foo {
struct impl { enum { red, yellow, green, NUM_COLORS }; };
public:
enum color { red = impl::red, yellow = impl::yellow, green = impl::green };
void command(color c);
};
不幸的是,有很多重复(我实际上原来键入green = impl::yellow;
但是如果你从不直接引用impl
的值那么无关紧要。)
否则,总有一个宏技巧:
#define MY_DEFINE_ENUM(Type, Elements) \
enum Type { BOOST_PP_SEQ_ENUM(Elements) }; \
inline size_t size(Type) { return BOOST_PP_SEQ_SIZE(Elements); }
使用邪恶的宏和模糊的预处理器机制来避免代码重复。它显然只适用于连续的枚举元素(它返回元素的数量,而不是最大数量)。
答案 2 :(得分:1)
在保护未来的维护者之间做出简单/容易的错误,并尝试阻止明显错误的事情,例如使用NUM_COLORS值,这是一条很好的界限。
在你的情况下,我建议在关键函数处断言输入并将其保留在那里。
我相信您可以使用专门的模板代理类和NUM_COLORS上的static_assert
来阻止用户将其传递到您的函数中。
我打出了似乎有用的东西。
class foo {
public:
enum color {
red,
yellow,
green,
NUM_COLORS
};
class Color_Rep
{
color c;
protected:
Color_Rep(color which_color) : c(which_color) { }
};
template <color C>
struct Color : public Color_Rep
{
Color() : Color_Rep(C) { }
enum { value = C };
};
private:
int bar[NUM_COLORS];
public:
void command(Color_Rep c);
};
// Deny access to command(NUM_COLORS).
template <>
struct foo::Color<foo::NUM_COLORS>
{
};
int main()
{
foo().command(foo::Color<foo::red>());
foo().command(foo::Color<foo::green>());
foo().command(foo::Color<foo::NUM_COLORS>()); // Won't compile.
}
答案 3 :(得分:0)
解决方案是使用地图:
std::map<color, unsigned> LEDs_in_stock;
LEDs_in_stock[red] += 2;
LEDs_in_stock[red]; // = 2
LEDs_in_stock[green]; // = 0
通过这种方式,您可以保持简洁,不需要硬编码任何大小。