在C++中防止将整数转换为枚举

20 浏览
0 Comments

在C++中防止将整数转换为枚举

假设我们有以下代码:

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

每个值都是从0到11的整数。然后我希望类型为Month的变量只能持有这些枚举值。所以这是创建变量的唯一正确方式:

Month m = Month::may;

但是语言允许以下其他方式:

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

这有点令人失望。如何只允许第一种方式?或者人们如何应对C ++中的枚举问题?

0
0 Comments

问题原因:

问题出现的原因是在C++中,可以将int类型的值直接转换为enum类型的值,这可能会导致编译时错误或运行时错误。

解决方法:

为了解决这个问题,可以使用一个普通的类来封装一个普通的枚举。通过这种方式,可以在编译时防止将int转换为enum。

以下是一个示例代码:

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(12345);

为了在运行时检查这种转换,可以使用如下代码:

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

然而,即使使用上述代码,仍然可以通过以下方式绕过编译时检查:

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

为了解决这个问题,可以在运行时添加额外的检查,如下所示:

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

以上就是解决在C++中将int转换为enum类型的问题的原因和方法。

0
0 Comments

在C++中,防止将int转换为enum的一个常见问题是,枚举类型无法限制只能使用枚举类型的方式进行初始化。为了解决这个问题,可以使用一个完整的类来替代枚举类型。下面是一个示例代码:

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; };
    // 其他月份的定义...
private:
    int v;
    constexpr Month(int v) noexcept: v(v) {}
};

这个类实现了一系列静态成员函数,每个函数都返回一个Month对象,表示对应的月份。这样,只能使用这些静态成员函数来创建Month对象,而不能直接将int转换为Month对象。

下面是对一些问题的回答:

- Is this complete or a concept (not in template sense)? 这是一个完整的实现,不是一个概念。

- Why constexpr and noexcept on the methods? 使用constexpr和noexcept修饰方法,是为了使这些方法可以在常量表达式和不能抛出异常的上下文中使用。

- And why give it a private member that is inaccessible and disallow ctor or setting it, but allow usage? 给类一个私有成员变量并禁止构造函数或设置它,但允许使用它的目的是为了防止将Month对象与底层的整数进行转换,并在语法上防止创建无效的值。

此外,还有一些关于使用该类的问题的回答:

- but will the default ctor then initialize v properly 默认构造函数会被隐式删除。

- Could one use Month bday = { 1 }; 不能使用这种方式进行初始化。

- Or would only Month bday = {Month::feb()}; // Month bday = Month::feb(); 是的,只能使用这种方式进行初始化。

建议在该类中添加比较运算符。在C++20中,可以使用以下代码轻松地实现这一点:

friend auto operator==(const Month&, const Month&) = default;
friend auto operator<(const Month&, const Month&) = default;

除此之外,我没有看到其他明显缺失的地方。

通过使用这个类,我们可以在不允许将int转换为enum的情况下创建一个更安全的"enum"。

0
0 Comments

在C++中,防止将int类型转换为枚举类型的问题主要出现在对枚举类型的使用上。枚举类型(包括作用域枚举)为底层类型的一些值提供了方便的名称,并且在作用域枚举的情况下禁止从该类型进行隐式转换。然而,它们并不限制对象只能取这些命名值,也不是为此而设计的。如果希望实现这种限制,可以创建一个带有验证函数的类来更改其状态。

然而,一般来说,这种方法的开销被认为不值得。按照通常的C++实践,假设您从不给枚举类型赋予不应该有的值。如果确实这样做了,为什么?那是一个bug:修复它。作用域枚举提供的隐式转换禁止应该使这些bug几乎不可能出现。如果有人费力地显式转换一个未命名的值?那是他们自己的错!文档化程序的行为将是“未定义”(根据您自己代码的API而不是语言规范),然后继续前进。

“作用域枚举提供的隐式转换禁止应该使这些bug几乎不可能出现。如果有人费力地显式转换一个未命名的值?那是他们自己的错!”——这并不是非常实用或有帮助的。问题的示例,即在枚举值和整数之间进行转换,是一个非常常见且容易出错的需求,例如与期望数字的现有库的交互(有时基于0,有时基于1),用于计算,用于序列化和反序列化。有些枚举需要担心,而其他枚举则不需要。

您可能认为这并不“有帮助”,但它是真实的。如果有其他方法来满足特定的用例,那么是不是更不容易出错?当然!但是我们生活在现实世界中,没有这种方法。

0