在循环中使用enum和值一致性

Using enum in loops and value consistency

本文关键字:一致性 enum 循环      更新时间:2023-10-16

我是c++强类型特性的忠实粉丝,我最喜欢的是在处理有限的数据集时使用枚举。

但是枚举缺少一些有用的特性,例如操作符:

enum class Hex : int
{
    n00, n01, n02, n03,
    n04, n05, n06, n07,
    n08, n09, n10, n11,
    n12, n13, n14, n15
};
for (Hex h = Hex::n0; h <= Hex::n15; ++h) // Oops! no 'operator ++'
{ /* ... */ }

很容易避免缺少操作符,在同一作用域中创建自由操作符:

Hex &operator ++(Hex &h)
{
    int r = static_cast<int>(Hex);
    h = static_cast<Hex>(r + 1);
    return h;
}
for (Hex h = Hex::n0; h <= Hex::n15; ++h) // Now the '++h' works!
{
    std::cout << std::dec << int(h) << ": "
              << std::hex << int(h) << 'n';
}

但是这种方法更麻烦,而不是解决问题,因为它可以打破枚举的值限制:当h等于Hex::n15时,应用++h将使h的值为16,这超出了Hex的值范围,而h仍然是Hex的类型,这个问题在其他枚举中更明显:

enum class Prime : int
{
    n00 = 2,   n01 = 3,   n02 = 5,   n03 = 7,
    n04 = 11,  n05 = 13,  n06 = 17,  n07 = 19,
    n08 = 23,  n09 = 29,  n10 = 31,  n11 = 37,
    n12 = 41,  n13 = 43,  n14 = 47,  n15 = 53
};
Prime &operator ++(Prime &p)
{
    // How to implement this?! will I need a lookup table?
    return p;
}

这个问题对我来说是个惊喜;我打赌,将不正确的值存储到枚举值中会引发异常。所以,现在我想知道是否有一种优雅的方法来处理这个枚举的弱点,我想实现的目标是:

  • 找到一种在循环中使用枚举值的舒适方式。
  • 确保操作间枚举数据的一致性。

额外的问题:

  • 当枚举数据获得超出其可能值的值时,是否有理由不抛出异常?
  • 有一种方法来推断与枚举类相关联的类型?,枚举HexPrime中的int类型。

正如你所注意到的,c++中的enum而不是枚举类型,但更复杂(或更混合)的东西。当你定义enum,您实际上定义了两件事:

  1. 整型,其合法范围足以包含或者所有枚举值的。(技术上说:范围是2^n - 1,其中n是所需的位数

  2. 一系列具有新定义类型的命名常量。

(我不确定会发生什么关于范围,如果你显式指定底层类型)

例如,给定enum Prime,合法值将为[0...64)范围内的所有整数,即使所有这些值没有名称。(至少如果你没有明确地说说它应该是一个int。) 可以不使用实现枚举的迭代器初始化;我有一个程序生成必要的代码。但它的工作原理是将值保持为整型它足够大,可以包含最大值+ 1。我的机器在这样的枚举上生成++的实现将assert,如果你试图增加超出结束。(注意,您的第一个示例将需要迭代h最后一个值:我实现的各种操作符没有允许这样做,这就是我使用迭代器的原因。)

关于为什么c++支持扩展范围:enum经常被使用定义位掩码:

enum X
{
    a = 0x01,
    b = 0x02,
    c = 0x04,
    all = a | b | c,
    none = 0
};
X operator|( X lhs, X rhs )
{
    return X((int)lhs | (int)rhs);
}
//  similarly, &, |= and &=, and maybe ~

有人可能会说,这种用法最好由一个类,但enum的使用是普遍的。

(fww:我的代码生成器不会生成++, --和枚举值中任何一个具有显式的定义的值,并且不会生成|, &等,除非全部这些值有明确定义的值)

关于为什么在外部转换某些值时没有错误合法的范围(例如100,对于X,上面)只是保持不变带着从C那里继承来的一般哲学:生活总比生活好比正确还要快。做额外的范围检查需要额外的运行时成本。

最后,关于你的最后一个例子:我不认为这是enum的实际使用。正确的解是anint[]。虽然c++ enum是一个相当复杂的品种,但我愿意仅将其用作真正的枚举类型,或用于位掩码(和只适用于位掩码,因为它是如此广泛建立成语)。

您可以使用switch:

class Invalid {};
Prime& operator ++(Prime& p)
{
    switch(p)
    {
        case n00: return n01;
        case n01: return n02;
        case n02: return n03;
        case n03: return n04;
        case n04: return n05;
        case n05: return n06;
        case n06: return n07;
        case n07: return n08;
        case n08: return n09;
        case n09: return n10;
        case n10: return n11;
        case n11: return n12;
        case n12: return n13;
        case n13: return n14;
        case n14: return n15;
        // Here: 2 choices: loop or throw (which is the only way to signal an error here)
        case n15: default: throw Invalid();
    }
}
但请注意,这是而不是对枚举的正确使用。我个人认为这很容易出错。如果要枚举整数,可以使用整数数组,或者对于素数,可以使用函数(在数学意义上:int nextPrime(int))。