控制枚举值的可见性

发布于 2024-10-29 07:45:43 字数 886 浏览 1 评论 0原文

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

Consider a C++ class that exports an enum, maintains an internal array over that enum, and wants to export a command that accepts values from the enum.

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

Is there a clean way to export only the actual colors, but not NUM_COLORS? I do not want to have to check for the edge case on every call when the compiler's type system really ought to be able to do it for me.

The obvious hack is:

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);
};

This of course is a ticking time bomb, waiting for some poor overworked maintenance programmer to add provisions for blue LEDs, and forget to update the NUM_COLORS line.

Let me clarify a bit. What I want, in this particular case, is to be able to say:

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

It is my understanding that C++ doesn't allow this.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

GRAY°灰色天空 2024-11-05 07:45:43

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

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

Is putting the enumeration into a base class an option?

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);
};

Personally I wouldn't do this, as it looks like an overly complex work around. I would simply forbid the caller to pass NUM_COLORS. True, the type system doesn't check that. But surely this is an easy thing to check for human programmers. Why would they pass NUM_COLORS?

七七 2024-11-05 07:45:43

我的第一个想法是尝试解决问题,但经过一番思考后,我会将负担转移到命令:

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); }

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

My first thought would be to try and solve the problem as you lay it, but after a bit reflexion, I would shift the burden to command:

void command(color c) {
  assert(0 <= c && c < NUM_COLORS && "Invalid argument");
}

Since enums are so weak types, you need to check the input anyway, as anyone could easily provide crappy arguments:

Foo foo;
foo.command(static_cast<Foo::color>(3)); // 3 is green, right ?

Original solution:

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);
};

Unfortunately there is a lot of duplication going on (and I actually originaly typed green = impl::yellow; though it does not matter if you never refer to impl's values directly).

Otherwise, there is always the macro trick:

#define MY_DEFINE_ENUM(Type, Elements)       \
  enum Type { BOOST_PP_SEQ_ENUM(Elements) }; \
  inline size_t size(Type) { return BOOST_PP_SEQ_SIZE(Elements); }

Which uses an evil macro and obscure preprocessor machinery to avoid code duplicaton. It obviously only works for consecutive enum elements (it returns the number of elements, not the maximum number).

挽你眉间 2024-11-05 07:45:43

保护未来的维护人员免于犯简单/容易的错误和尝试阻止应该明显错误的事情(例如使用 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.
}

There's a fine line between protecting your future maintainers from making simple/easy mistakes, and trying to stop something that should be obviously wrong, such as using the NUM_COLORS value.

In your case I'd suggest asserting the input at key functions and leaving it at that.

I believe you could use a template proxy class that specializes and static_asserts on NUM_COLORS to prevent users passing it into your functions.

I typed something out that seems to work.

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.
}
呆头 2024-11-05 07:45:43

一个解决方案是使用映射:

std::map<color, unsigned> LEDs_in_stock;

LEDs_in_stock[red] += 2;

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

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

A solution would be to use a map:

std::map<color, unsigned> LEDs_in_stock;

LEDs_in_stock[red] += 2;

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

This way you keep you enum clean, and do not need to hard-code any size.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文