enum の話

enum を使うと、連番のシンボルが自動的に生成されるので要素数の増減、特に中抜けが発生する可能性が高い場合には重宝します。

で、enum は列挙名をつけることが出来て、列挙名は型の様な振る舞いをします。そしてC++ の厳密な型チェックを利用すると意図しない値を使っていないかチェックすることができます。

しかし、次のような場合。スレッドの優先度は5段階で指定したい。けど将来にこの数が変わることはあり得る。不正な値を指定したときにはコンパイル時に気付くように引数・戻り値の型は列挙型にしておこう。という場合ですが、

// スレッドの優先度
enum thread_priority {
    thread_pri_highest,
    thread_pri_high,
    thread_pri_middle,
    thread_pri_low,
    thread_pri_lowest
};

// スレッドの優先度取得
thread_priority GetThreadPriority();
// スレッドの優先度設定
void SetThreadPriority(thread_priority pri);

// スレッド
void thread() {

    /* ... */

    // スレッドの優先度を取得して、
    thread_priority tp = GetThreadPriority();
    // 今よりひとつ低い優先度を設定
    SetThreadPriority(tp + 1);  // <- 型が違う!

    /* ... */

}

新たに走らせるスレッドの優先度を今のスレッドより高く、あるいは低くしたいという場面は、あると思います。 そんなときには足し算、引き算をしたいところですが、そうすると列挙型だった優先度の値が整数に暗黙に変換されてしまい、この例ではSetThreadPriority に渡す部分でコンパイルエラーが出ます。 それでは、と

    SetThreadPriority(static_cast<thread_priority>(tp + 1));  // <- 型が違うのでキャスト

これでコンパイルエラーはなくなりましたが、もしtp の値が最大値のthread_priority_lowest だったら?せっかく不正な値をはじくために引数の型を列挙型にしたのにキャストを使ってしまうと実行時エラーの可能性が出てきてしまいます。


と言うわけで、シンボルの連番を維持したいけれども値を計算で出す可能性がある場合には、シンボル定義にはenum を使いつつもそれを扱う型は整数型にしといた方が良い、という結論に至ったのでした。そんで計算結果の範囲チェックとセットで使いましょう。

// スレッドの優先度
enum {
    thread_pri_highest,
    thread_pri_high,
    thread_pri_middle,
    thread_pri_low,
    thread_pri_lowest
};
// スレッドの優先度を扱う型定義
typedef int thread_priority;

// スレッドの優先度取得
thread_priority GetThreadPriority();
// スレッドの優先度設定
void SetThreadPriority(thread_priority pri);

// スレッド
void thread() {

    /* ... */

    // スレッドの優先度を取得して、
    thread_priority tp = GetThreadPriority();
    // 今よりひとつ低い優先度を設定
    tp += 1;
    // 範囲チェック
    assert(thread_pri_highest <= tp && tp <= thread_pri_lowest);
    SetThreadPriority(tp);

    /* ... */

}