防止在C 中枚举枚举

Preventing casting ints to enums in C++

本文关键字:枚举      更新时间:2023-10-16

假设我们有

enum class Month {jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec};

每个值是一个int,0到11。然后,我希望类型月的变量仅保存这些枚举值。因此,这是创建变量的唯一确定方法:

Month m = Month::may;

但是,这是语言允许的其他一些方法:

Month m1 = Month(12345);
Month m2 = static_cast<Month>(12345);

这有些令人失望。我只允许第一种方式?或人们如何应对C 中的不良枚举?

我只允许第一种方式?

用枚举不可能。

如果您想要一个不能从(可能是无效的(值中明确转换的白痴"枚举",则可以使用完整的吹式类代替枚举。不幸的是涉及一些样板:

struct Month {
    constexpr int value() noexcept { return v; }
    static constexpr Month jan() noexcept { return 0; };
    static constexpr Month feb() noexcept { return 1; };
    static constexpr Month mar() noexcept { return 2; };
    static constexpr Month apr() noexcept { return 3; };
    static constexpr Month may() noexcept { return 4; };
    static constexpr Month jun() noexcept { return 5; };
    static constexpr Month jul() noexcept { return 6; };
    static constexpr Month aug() noexcept { return 7; };
    static constexpr Month sep() noexcept { return 8; };
    static constexpr Month oct() noexcept { return 9; };
    static constexpr Month nov() noexcept { return 10; };
    static constexpr Month dec() noexcept { return 11; };
private:
    int v;
    constexpr Month(int v) noexcept: v(v) {}
};

可以通过使用封装常规枚举的常规类可以解决您的问题,例如在以下示例中:

class Month  {
public:
  enum Type {
    jan, feb, mar, apr, may, jun,
    jul, aug, sep, oct, nov, dec
  };
  Month(Type t);
private :
  Type type;
};

然后以下会产生编译时错误:

  Month mm = Month::jan;
  Month m1 = Month(12345);
  Month m2 = static_cast<Month>(12345);
  e.cpp:27:25: error: invalid conversion from 'int' to 'Month::Type' [-
  fpermissive]
      Month m1 = Month(12345);
  e.cpp:26:42: error: invalid conversion from 'int' to 'Month::Type' [-
  fpermissive]
      Month m2 = static_cast<Month>(12345);

仍然有一个可能的,但更复杂的方案

 Month m1 = Month(Month::Type(12345));

但是,可以动态检查一下

 Month::Month(Type t) : type(t){
  if (int(t) < 0 || int(t) > int(dec)) {
      throw "error";
  }
}

然后,我期望类型月的变量仅容纳这些枚举值

然后,您误解了枚举(甚至是范围的枚举(。它们为您提供了基本类型的一些值的方便名称(并且,在范围内的枚举的情况下,禁止从该类型的隐式转换(。他们不将对象限制为那些命名值,也不是打算的。如果您想这样做,请在更改其状态的任何事物中创建一个具有验证例程的类。

但是,这种方法的开销通常不值得。遵循通常的C 练习,只是假定您永远不会给枚举一个您不应该的价值。如果你这样做,为什么?那是一个错误:修复它。范围内的枚举提供的隐式转换的禁令应该使这些错误消失了。如果有人竭尽全力明确转换一个未命名的价值?那是他们自己的错!记录程序的行为将"未定义"(不是根据语言而是根据您自己的代码的API(并继续。

您不能禁止在不修改语言本身的情况下允许语言允许的内容。毕竟,这是完全有可能的:

Month m;
int * val = (int *) &m;
*val = -46;

...没有什么可以阻止您的。关键是人们通常不应该这样做,如果这样做,他们通常有很好的理由。

如果您想执行更强大的编码策略,则需要一个支持该类型诊断的编译器(通常是通过警告标志,通常可以转换为硬错误(,或者仅仅将其简单地转换为其他语言。如果这是不可接受的,通常人们只是不像我发布的示例那样编写疯狂的代码来应对这样的问题。