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

しかし、このアプローチは、列挙型の値の制限を破る可能性があるため、解決策というよりも迷惑です: while equals を適用すると++hhhHex::n15が値 16 に設定されHexます。この問題は、他の列挙でより明白です。hHex

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列挙型HexおよびPrime.
4

2 に答える 2

4

お気づきのようにenum、C++ では列挙型ではなく、より複雑な (またはより混合された) ものです。を定義するとき enum、実際には次の 2 つのことを定義します。

  1. 列挙値の 1 つまたはすべてを含むのに十分な正当な範囲を持つ整数型。(技術的には、範囲は です2^n - 1。ここnで、 は最大値を保持するために必要なビット数です。)

  2. 新しく定義された型を持つ一連の名前付き定数。

(基になる型を明示的に指定した場合、範囲に関して何が起こるかわかりません。)

enum Primeたとえば、あなたの が与えられた[0...64)場合、これらの値のすべてに名前がなくても、正当な値は範囲内のすべての整数になります。(少なくとも、それが であるべきだと具体的に言わなければint。)

イニシャライザなしで列挙型のイテレータを実装することは可能です。必要なコードを生成するプログラムがあります。ただし、最大値に 1 を加えた値を格納するのに十分な大きさの整数型で値を維持することによって機能します。私のマシンで生成さ++れたそのような列挙型 の実装はassert、最後を超えてインクリメントしようとした場合に発生します。(最初の例では、最後の値を超えて1つ繰り返す必要があることに注意してください。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 など) を変換してもエラーが発生しない理由は、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の配列を使用してこれを行うことができます。素数の場合は、関数(数学的な意味で:)を使用できますint nextPrime(int)

于 2012-12-20T12:01:49.690 に答える