C++でいう「定数」というのは、例えば「1234」のような即値のことです。それはそれとして、ここでは何らかの名前を使って値を参照することができる、一般的な意味での定数について解説することにします。値を変更できない変数のようなものと考えてもいいでしょう。
const修飾子を付けたオブジェクト
「変数を使う」および「続・変数を使う」の回で紹介したのとほとんど同じ方法で、定数を宣言することができます。異なる点は、型にconstを付けることだけです。
0 1 2 3 |
int a = 123; // 変数 const int b = 456; // 定数 |
定数の場合、値が不定になってしまうと後から変更することができません。そのため、必ず初期値が必要になります。ただし、std::stringのように初期値がなくてもコンストラクタによって初期化されるものは初期値を省略することができます。
C++では、constを付けて宣言した整数型のオブジェクトは定数式として扱うことができます。つまり、配列の要素数やswitc 文におけるcaseラベル、列挙の値に使うことができます。ただ、現在ではこうした用途には後述のconstexprを使うことの方が多くなっていると思います。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
const int constant = 10; int array[constant]; // 配列の要素数 enum { enumerator = constant // 列挙の値 }; int main() { switch (constant) { case constant: // case ラベル break; default: break; } ... } |
constを用いた定数のデメリットとしては、一種のオブジェクトですのでメモリを消費してしまう点にあります。しかし、多くの処理系では、最適化を完全に抑止した場合を除き、整数型のconst付き定数はメモリを消費しません。ただし、&constantのように、定数へのポインタを取得しようとした場合は別です。浮動小数点数や配列などは、プログラム中で実際に参照しない場合には、最適化によってメモリを消費しない処理系もあれば、消費する処理系もあります。明示的なコンストラクタやデストラクタを持つクラス型や、仮想関数を持つクラス型、仮想継承したクラス型のconst付き定数は、まず間違いなくメモリを消費します。
列挙定数
整数の定数値を扱う一番簡単な方法は列挙定数を使うことです。先ほどのconst修飾子を付けたオブジェクトのところでも少し出てきましたが、列挙定数は次のように定義します。
0 1 2 3 4 5 6 7 |
enum tag { a = 123, b = 456, c = 789 }; |
このとき、タグ名(上の例ではtag)は省略することができます。列挙定数は、intのような汎用的な整数型ではなく、例えば上の例でいえばtag型になります。これはメリットにもデメリットにもなります。すなわち、ズバリint型の定数が欲しい場合には、列挙定数は不向きだということになります。一方で、新しい型が定義できるということは、関数などを列挙型で多重定義することができることを意味します。この場合には、列挙定数はかなり強力です。何でもよいので整数型の定数が欲しい場合にも列挙定数は有効です。
列挙定数の最大のメリットは、値に付けられた名前がデバッガでも使えるということです。これはデバッガの機能に依存しますので、常に使えるというわけではありませんが、多くの環境で利用することができるはずです。また、列挙定数も定数式として扱うことができますので、配列の要素数や、switch文におけるcaseラベル、列挙子の値に使うことができます。
ところで、C++11以降では列挙体の仕様が拡張され、次のように基礎となる型を指定することができるようになっています。
0 1 2 3 4 5 6 7 |
enum tag : short { a = 123, b = 456, c = 789 }; |
上の例ではtagの基礎となる型はshort型ですので、tag型のサイズはshort型と同じになります。さらに、C++以降では次のような書き方もできます。
0 1 2 3 4 5 6 7 |
enum class tag { a = 123, b = 456, c = 789 }; |
このようにタグ名の前にclassを書くことで、列挙定数にアクセスする際にはtag::aのように型名を明示的に指定することが強制されます(classを書かなくても、同様に型名を明示的に指定することはできます)。
マクロ
#define指令で定義したマクロを定数とする方法は、C言語では非常によく使われます。しかし、そうしたマクロは有効範囲が適用されず、名前空間も利用できないため、いろいろな弊害をもたらします。しかし、ときにはマクロでなければ実現できない定数もあるにはあります。例えば、
0 1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> #define a 0x123 #define STR(x) STR2(x) #define STR2(x) #x int main() { std::cout << STR(a) << std::endl; } |
のように、16進法で表記されたという情報を保持したい場合には、マクロでなければ実現することができません。このような特殊なケースを除き、定数を扱うためにマクロを使用することは避けた方が無難です。
constexpr指定子を用いた定数
constexpr指定子はC++11から導入された仕様で、定数式(const expression)の意味です。const修飾子を用いた場合に定数式になるのは整数型だけでしたが、constexpr指定子の場合は浮動小数点数など整数型以外も定数式に展開することができます。
使い方はいたって簡単で、次のように書きます。
0 1 2 3 4 |
constexpr int a = 123; constexpr double b = 4.56; constexpr const char c[] = "abc"; |
クラスであってもconstexpr指定子を付けたコンストラクタがある場合は、constexpr指定子を付けて定数にすることができます。例えばC++17で導入されたstd::string_viewクラスであれば、
0 1 2 |
constexpr std::string_view sv("xyz"); |
というように、普通にクラス型の変数を定義するときと同じように、単にconstexpr指定子を付けることで定数を定義することができます。
まとめ
定数を使うための方法を4種類ご紹介しました。では、実際にはどれを使うのが一番よいのかということになるわけですが、基本は constexr指定子を付けたオブジェクトを使うことをお勧めします。処理系が準拠する規格が古い(C++03までの)場合や翻訳単位間でリンクする必要がある場合はconst修飾子を付けたオブジェクトを使うといいでしょう。また、列挙定数を使う積極的な理由があるなら、列挙定数を使います。マクロでなければ実現できない状況を除き、マクロの利用は控えるようにしましょう。