C++ でいう「定数」というのは、例えば 1234 のような即値のことです。しかし、何らかの名前を使ってそれらの値を参照することができる、一般的な意味での定数について解説することにします。
const 修飾子を付けたオブジェクト
変数を使うのところで紹介したのとほとんど同じ方法で、定数を宣言することができます。異なる点は、型に const を付けることだけです。
const int b = 456; // 定数
定数の場合、値が不定になってしまうと後から変更することができません。そのため、必ず初期値が必要になります。ただし、std::string のように、初期値がなくても初期化されるものは初期値を省略することができます。
C++ では、const を付けて宣言した整数型のオブジェクトは、定数式として扱うことができます。つまり、配列の要素数や、switch 文における case ラベル、列挙の値に使うことができるのです。
int array[constant]; // 配列の要素数
enum
{
enumerator = constant // 列挙の値
};
int main()
{
switch (constant)
{
case constant: // case ラベル
break;
default:
break;
}
...
}
const を用いた定数のデメリットとしては、一種のオブジェクトですのでメモリを消費してしまう点にあります。しかし、多くの処理系では、最適化を完全に抑止した場合を除き、整数型の const 付き定数はメモリを消費しません。ただし、&constant のように、定数へのポインタを取得しようとした場合は別です。浮動小数点数や配列などは、プログラム中で実際に参照しない場合には、最適化によってメモリを消費しない処理系もあれば、消費する処理系もあります。明示的なコンストラクタやデストラクタを持つクラス型や、仮想関数を持つクラス型、仮想継承したクラス型の const 付き定数は、まず間違いなくメモリを消費します。
列挙定数
整数の定数値を扱う一番簡単な方法は列挙定数を使うことです。先ほどの const 修飾子を付けたオブジェクトのところでも少し出てきましたが、列挙定数は次のように定義します。
{
a = 123,
b = 456,
c = 789
};
このとき、タグ名(上の例では tag)は省略することができます。列挙定数は、int のような汎用的な整数型ではなく、例えば上の例でいえば tag 型になります。これはメリットにもデメリットにもなります。すなわち、ズバリ int 型の定数が欲しい場合には、列挙定数は不向きだということになります。一方で、新しい型が定義できるということは、関数などを列挙型で多重定義することができることを意味します。この場合には、列挙定数はかなり強力です。何でもよいので整数型の定数が欲しい場合にも列挙定数は有効です。
列挙定数の最大のメリットは、値に付けられた名前がデバッガでも使えるということです。これはデバッガの機能に依存しますので、常に使えるというわけではありませんが、多くの環境で利用することができるはずです。また、列挙定数も定数式として扱うことができますので、配列の要素数や、switch 文における case ラベル、列挙の値に使うことができます。
マクロ
#define 指令で定義したマクロを定数とする方法は、C では非常によく使われます。しかし、そうしたマクロは有効範囲が適用されず、名前空間も利用できないため、いろいろな弊害をもたらします。しかし、ときにはマクロでなければ実現できない定数もあるにはあります。例えば、
#define a 0x123
#define STR(x) STR2(x)
#define STR2(x) #x
int main()
{
std::cout << STR(a) << std::endl;
}
のように、16 進法で表記されたという情報を保持したい場合には、マクロでなければ実現することができません。このような特殊なケースを除き、定数を扱うためにマクロを使用することは避けた方が無難です。
まとめ
定数を使うための方法を 3 種類ご紹介しました。では、実際にはどれを使うのが一番よいのかということになるわけですが、基本は const 修飾子を付けたオブジェクトを使うことをお勧めします。コンパイラの不具合等で、const 修飾子を付けたオブジェクトがうまく機能しない場合や、列挙定数を使う積極的な理由がある場合に限り、列挙定数を使うようにしましょう。マクロでなければ実現できない状況を除き、マクロの利用は控えるようにしましょう。

