4

我非常喜欢 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';
}

但是这种方法比解决方案更令人讨厌,因为它可以打破枚举的值限制:应用++hwhile hequals toHex::n15会将 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;
}

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

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

附加问题:

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

2 回答 2

4

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

  1. 具有足以包含一个或所有枚举值的合法范围的整数类型。(技术上:范围是2^n - 1,其中n是保持最大值所需的位数。)

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

(如果您明确指定基础类型,我不确定范围会发生什么。)

例如,给定您enum Prime的 合法值将是 range 中的所有整数[0...64),即使所有这些值都没有名称。(至少如果你没有明确说它应该是一个int.)

可以在没有 初始化器的情况下为枚举实现迭代器;我有一个生成必要代码的程序。但它通过将值保持在一个整数类型中来工作,该类型足够大以包含最大值加一。如果您尝试增加超出末尾,我的机器将++在这样的枚举上 生成实现。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 ~

有人可能会争辩说,这种用法最好由一个类来处理,但enumfor 它的使用无处不在。

(FWIW:如果任何枚举值具有明确定义的值,我的代码生成器将不会生成++,和迭代器,并且除非所有值都具有明确定义的值,否则不会生成等。)--|&

至于为什么在转换合法范围之外的某些值(例如 100,for ,以上)时没有错误,这X完全符合从 C 继承的一般哲学:快比正确更好。进行额外的范围检查将需要额外的运行时成本。

最后关于你的最后一个例子:我不认为这是对enum. 这里正确的解决方案是 int[]. 虽然 C++enum是一个混合品种,但我只会将它用作真正的枚举类型,或用于位掩码(并且仅用于位掩码,因为它是如此广泛建立的习语)。

于 2012-12-20T12:29:50.147 回答
1

您可以使用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)

于 2012-12-20T12:01:49.690 回答