4.

C++で定数を宣言する方法|const・constexpr・#defineの違い

編集

C++で定数を宣言するには、変数の型の前に const を付けて const int MAX = 100; のように書く。これで MAX は再代入できない読み取り専用の値になる。コンパイル時に値を確定させたい場合は C++11 以降の constexpr を使い、古いコードで見かける #define マクロは型を持たないため現在は constconstexpr が推奨される。

この記事の要点
  • 定数は const 型 名前 = 値; で宣言し、宣言後の再代入はコンパイルエラーになる。
  • const は宣言時に必ず初期化が必要。後から値を入れることはできない。
  • constexpr(C++11以降)はコンパイル時に値が確定する定数で、配列サイズなどにも使える。
  • #define は単なる文字列置換のマクロで型を持たない。型安全な const / constexpr が推奨される。
  • ポインタでは const の位置で「指す先が定数」か「ポインタ自身が定数」かが変わる。
  • 関連する基本は 変数の宣言 を参照。

 

constによる定数の宣言

もっとも基本的な定数の宣言は const キーワードを使う方法である。型の前(または後ろ)に const を付けると、その変数は初期化した値のまま変更できなくなる。

const int MAX = 100;

const double PI = 3.14159;

MAX = 200;   // エラー:再代入はできない

const int MAX = 100;int const MAX = 100; は同じ意味で、どちらも有効である。一般には型の前に書く前者の書き方が広く使われている。

重要なのは、const宣言と同時に初期化が必須という点である。次のように分けて書くことはできない。

const int a;   // エラー:初期化されていない定数

a = 1;       // ここで代入することもできない

 

constexprによるコンパイル時定数(C++11)

C++11 で導入された constexpr は、コンパイル時に値が確定する定数を宣言するためのキーワードである。const は「実行時に変更されない」ことを保証するが、値の確定がコンパイル時とは限らない。一方 constexpr は、その値がコンパイル時に計算可能であることをコンパイラに要求する。

constexpr int SIZE = 10;

int buffer[SIZE];   // コンパイル時定数なので配列サイズに使える

配列のサイズやテンプレート引数など、コンパイル時に値が決まっていなければならない場所では constexpr が役立つ。関数に付けて、コンパイル時に計算可能な関数を定義することもできる。

constexpr int square(int x) {

    return x * x;

}

constexpr int N = square(5);   // コンパイル時に 25 が確定する

コンパイル時に値が確定できるものは、できるだけ constexpr で宣言すると意図が明確になり、実行時のコストも避けられる。

 

#defineマクロとの違い

C言語の頃から使われてきた #define でも定数のような名前を定義できる。しかしこれはプリプロセッサによる単純な文字列置換であり、型を持たない点が const / constexpr と根本的に異なる。

// プリプロセッサが MAX をそのまま 100 に置き換えるだけ

#define MAX 100

int buffer[MAX];

#define には次のような弱点がある。

  • 型が無い:型チェックが効かず、意図しない変換が起きても警告されにくい。
  • スコープを無視する:名前空間やブロックに関係なく、ファイル全体(インクルード先も含む)で置換される。
  • デバッグしづらい:置換後の値しか残らず、デバッガで名前を追えないことがある。

これらの理由から、現在のC++では定数に #define を使うことは避け、型安全な constconstexpr が推奨される

 

constの位置:ポインタの定数

ポインタに const を付ける場合、const を書く位置によって意味が変わるため注意が必要である。大きく分けて「指す先の値が定数」か「ポインタ自身が定数」かの2種類がある。

int x = 1, y = 2;

 

// (1) 指す先の値が定数(ポインタの付け替えは可)

const int* p = &x;

*p = 10;  // エラー:値は変更できない

p = &y;   // OK:指す先は変えられる

 

// (2) ポインタ自身が定数(指す先の値は変更可)

int* const q = &x;

*q = 10;  // OK:値は変更できる

q = &y;   // エラー:付け替えできない

覚え方として、const は「すぐ右にあるものを固定する」と考えると整理しやすい。const int*int(=指す先の値)を固定し、int* const*(=ポインタ)を固定する。両方を固定したい場合は const int* const と書く。

 

enumによる定数群

関連する複数の定数をまとめて扱いたいときは、列挙型 enum が便利である。連続した整数値に名前を付けたいケースで特に有効である。

enum Color {

    RED,    // 0

    GREEN,  // 1

    BLUE    // 2

};

Color c = RED;

値を明示しなければ 0 から順に割り当てられる。C++11 以降では、より型安全な enum class も利用できる。enum class では Color::RED のようにスコープ付きでアクセスし、名前の衝突や暗黙の整数変換を防げる。

enum class Color { RED, GREEN, BLUE };

Color c = Color::RED;   // スコープ付きでアクセスする

 

const・constexpr・#define・enumの比較

定数を表す主な手段を整理すると次のようになる。基本的には型安全な const / constexpr を選び、コンパイル時の確定が必要なら constexpr を使うのがよい。

手段 値の確定 スコープ 主な用途
constexpr あり コンパイル時 従う 配列サイズ等の定数(C++11以降)
const あり 実行時もあり得る 従う 一般的な変更不可の値
#define なし 前処理時の置換 無視する 条件コンパイル等(定数には非推奨)
enum 整数系 コンパイル時 従う 関連する整数定数のグループ化

 

関連する基本:変数の宣言

定数は「再代入できない変数」とも言える。値を保持する仕組みそのものは通常の変数と共通しているため、まず変数の宣言と初期化を理解しておくと const の挙動も把握しやすい。詳しくは 変数の宣言 を参照してほしい。

 

落とし穴

よくある落とし穴
  • 初期化忘れconst int a; のように値を与えずに宣言するとコンパイルエラーになる。const は宣言時に必ず初期化する。
  • const外しconst_cast などで強引に const を外して値を書き換える行為は、もとが本当に定数だった場合に未定義動作を招く。安易に使わない。
  • #defineの罠#define HALF 1/2 のように書くと展開後に意図しない計算順序になることがある。マクロは置換でしかないため、必要なら括弧で囲む。そもそも定数なら const / constexpr を使う。
  • ポインタのconst位置の取り違えconst int*int* const は意味が逆。どちらを固定したいのかを意識して書く。

 

よくある質問(FAQ)

Q. constconstexpr はどちらを使えばよいですか。
A. コンパイル時に値が確定できるなら constexpr を、そうでない(実行時に決まる値を変更不可にしたい)なら const を使うとよい。配列サイズやテンプレート引数のようにコンパイル時定数が必須の場所では constexpr が必要になる。

Q. 定数の名前はどう書くのが一般的ですか。
A. 慣習として MAX_SIZE のように大文字とアンダースコアで書くスタイルがよく使われる。ただし言語規則ではないため、プロジェクトの命名規約に従うのがよい。constexpr では小文字で書くスタイルも見られる。

Q. クラスのメンバを定数にできますか。
A. できる。クラス内で static constexpr を使うと、クラスに属するコンパイル時定数を宣言できる。インスタンスごとに固定したい場合は const メンバを使い、コンストラクタの初期化リストで初期化する。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. コメントアウト
  2. 文字列の結合/連結
  3. 変数の宣言
  4. 定数の宣言
  5. if文
  6. if文の論理演算子
  7. for文
  8. データ型(文字列以外)
  9. データ型(文字列)
  10. 配列とfor文
  11. 配列の要素数
  12. 多次元配列とfor文
  13. 多次元配列の要素数
  14. 関数の定義と呼び出し

最近更新/作成されたページ