※この記事は http://k_satoda.users.sourceforge.net/article/cxx-enum-last-num.html を元に実験的に転載したもので、内容は基本的に同じです。
何の話?
C, C++ での列挙宣言 enum について、列挙子の数が欲しいことがよくあります。例えば各列挙子に対応する情報を取り出すためのテーブルを以下のように簡単な配列で定義して対応付けを行いたい場合などです。
int PriceOf(enum Item x)
{
static int const price[NumItems] = {
123, // Item_A の値段
456, // Item_B の値段
789, // Item_C の値段
};
return price[x];
}
この列挙子の数 NumItems
を下記のように定義する手法があります。
enum Item
{
Item_A,
Item_B,
Item_C,
NumItems
};
このように NumItems
を定義すると、このあと Item_D
, Item_E
, ... などが増えていっても NumItems
が最後においてある限り NumItems
は「列挙子の数」として正しい値であり続けます。わずらわしいメンテナンスが不要であり、便利です。
この手法は便利なのでよく見かけるのですが、僕はこの手法には見落とされがちなデメリットがあると考えています。
何が問題?
-
enum Item
型の引数を受け取る関数にNumItems
が渡されてもエラーにならない。 -
NumItems
を関数テンプレートの引数に渡すとenum Item
で特殊化されてしまう。 -
-Wswitch
が有効利用できない。
じゃぁどうすれば?
ここでは下記のような手法を提案します。(コメントも「手法」に含みます。)
enum Item
{
Item_A,
Item_B,
Item_C
// ※最後に追加するときは↓も更新してね。
};
int const NumItems = Item_C + 1;
ここで Item_C
の後ろにカンマを置いていないのは意図的なもので、コメントを見ないで追加するような迂闊なプログラマにコンパイルエラーを見舞って注意を促すための策です。
それはちょっと・・・どうだろうか?
提案の手法では明らかに、最初に挙げた手法が解決してくれたはずの列挙子の数のメンテナンスが問題として復活しています。しかしどちらの手法でもそれぞれに問題があるのであれば、どちらにするかはそれらの問題の大小で考えるべきでしょう。
まず最初に挙げた手法の問題は、列挙型を利用する箇所の数に比例して大きくなります。次に提案の手法の問題(更新ミスを起こす可能性)は、列挙の最後に追加などの編集が加えられる回数に比例して大きくなります。
僕の結論は、列挙型を利用する箇所の数は時間が経つにつれて発散していくのに対して列挙の最後が編集される回数は時間が経つにつれて収束していくので、提案の手法を基本として使うほうが問題は小さくなることが多いだろう、というものです。
理想を言えば、コンパイラから NumItems
みたいな情報を取り出せるようになっててくれたらいいんですけどね。