【问题标题】:Controlling visibility of enum values控制枚举值的可见性
【发布时间】:2011-07-27 20:11:42
【问题描述】:

考虑一个导出枚举的 C++ 类,在该枚举上维护一个内部数组,并希望导出一个接受枚举值的命令。

class foo {
public:
  enum color {
    red,
    yellow,
    green,
    NUM_COLORS
  };
private:
  something somebody[NUM_COLORS];
public:
  void command(color c);
};

有没有一种干净的方法可以只导出实际颜色,而不是 NUM_COLORS?当编译器的类型系统真的应该能够为我做这件事时,我不想在每次调用时都检查边缘情况。

明显的 hack 是:

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++ 不允许这样做。

【问题讨论】:

  • 据我所知,您尝试做的事情实际上是不可能的。想到的下一个最好的事情是非常努力地按住你的 shift 键并在枚举下方写一个非常重要的评论。
  • 编译器不会帮助你摆脱愚蠢。无论如何,有人可以很容易地写出 foo::color(7)。

标签: c++ arrays enums visibility


【解决方案1】:

将枚举放入基类是一种选择吗?

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

【讨论】:

  • 下一个问题:如何隐藏foo_enums?任何人都可以使用foo_enums::NUM_COLORS...
  • @André:把它放在 detail 命名空间中,每个理智的程序员都知道他们甚至不应该考虑触摸它。
  • @Andre 任何人都可以取消引用空指针。我们如何阻止他们这样做?如果他们无法阅读并遵循评论说“这个枚举常量只能由类“foo”使用,那很遗憾。引用 Herb Sutter:记住区分“防止墨菲和防止马基雅维利。”
  • 在这种情况下,我想要的语言是允许我说 enum foo {baz, bar, waldo};然后是 int cruft[foo];。我可以接受语言的限制,即只有在没有枚举值具有值覆盖时才允许这样做(例如:enum mangled_foo {baz, bar=43, waldo};)。
【解决方案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); }

它使用邪恶的宏和晦涩的预处理器机制来避免代码重复。它显然只适用于连续的枚举元素(它返回元素的数量,而不是最大数量)。

【讨论】:

  • 这看起来就像我要在 C++ 中得到我想要的一样接近。它还向我展示了如何解决想要导出一些但不是全部、不一定是连续的枚举文字的概括(我认为这很痛苦)。似乎有些东西是 C++ 设计委员会不理解的。
  • @John R. Strohm:我同意,C++ 中的enum 有点损坏。其中嵌入了两个概念:仅指定枚举的能力和将值“映射”到名称的能力。它们在语义上是不同的,将它们放在一个单一的概念中会让人很尴尬(而且缺乏自省,我不明白为什么它们不提供编译时自省,因为它是免费的)。
  • 我对内省、编译时或其他方面的了解不够,无法直接发表评论。我观察到我想要的功能自 1983 年正式发布的第一个语言版本以来一直存在于 Ada 中。
  • @John R. Strohm:我希望它是在 C++ 中:/
【解决方案3】:

在保护您未来的维护者不犯简单/容易的错误和试图阻止应该明显错误的事情之间有一条很好的界限,例如使用 NUM_COLORS 值。

在您的情况下,我建议在关键功能处声明输入并将其保留。

我相信您可以使用专门的模板代理类和 NUM_COLORS 上的static_asserts 来防止用户将其传递给您的函数。

我输入了一些似乎有用的东西。

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.
}

【讨论】:

  • 如果我学到了一件事,那就是期望人们不要做明显错误的事情,这是一种鲁莽的练习。他们会这样做,保证,除非你阻止他们。示例:北电网络软件组有一个书面的硬性和快速的无例外策略“在取消引用指针之前,您应该测试 null。”猜猜一些无名的编码员做了什么? (猜猜谁来追踪由此产生的间歇性硬崩溃?)
  • @John R. Strohm 我个人认为北电的例子与这种情况完全不同。 容易忘记检查指针是否为空(我不会争论这是否是个好主意),但在我看来,使用枚举值 NUM_COLORS 是完全不同的:一个明确的决定编码器使用它。我们是否千方百计阻止人们访问#define private public
【解决方案4】:

解决方案是使用地图:

std::map<color, unsigned> LEDs_in_stock;

LEDs_in_stock[red] += 2;

LEDs_in_stock[red]; // = 2
LEDs_in_stock[green]; // = 0

这样可以让你的枚举保持干净,并且不需要硬编码任何大小。

【讨论】:

  • 我看了一下地图头文件,乍一看似乎背了很多包袱。我需要拼凑一个测试程序,看看实际生成了什么样的代码。我不能接受一个解决方案,它会为简单的数组引用强加成百上千条指令开销,我不应该这样做。
  • 考虑到地图节点的开销与它糟糕的有效负载有关,这似乎有点矫枉过正。
  • 当然重,但我觉得C++代码比二进制代码更重要。最好有一个非常简单、安全且可以工作的 C++ 代码来生成更大的二进制文件,因为处理器执行它不会很痛苦。而程序员维护一个hacky代码是一件痛苦的事情。这是我个人会选择的解决方案,但这当然取决于你^^
  • 不幸的是,我的讨论领域是资源有限的嵌入式系统。
猜你喜欢
  • 2013-07-22
  • 2011-11-15
  • 2014-08-22
  • 1970-01-01
  • 2011-06-29
  • 2015-01-23
  • 1970-01-01
  • 2018-12-28
  • 2013-06-23
相关资源
最近更新 更多