Menu

C++ の const と定数: const、constexpr、consteval

C++ で const を使って読み取り専用の値を宣言する方法、const と constexpr の違い、const ポインタと const へのポインタの違い、そして const メンバ関数について解説します。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

定数が存在する理由

定数とは、いったん設定したら二度と変更しないと約束する値です。何かを const と印付けることは、同時に 2 つの役割を果たします。コードを読む人に対して意図を文書化し、さらにコンパイラがその意図を強制できるようにします。値を変更しようとする行は、静かな実行時バグではなくコンパイルエラーになります。

auto キーワード が変数の型をコンパイラに推論させるのに対し、const はその変数に対してできることを制約します。この 2 つは自由に組み合わせられます。const auto limit = 100; は読み取り専用の int です。

const 値の宣言

const を型の前に置きます。const 変数は同じ行で必ず初期化しなければなりません。あとから代入を許される瞬間は存在しないからです。

代入のコメントを外すとプログラムはコンパイルできません。コンパイラは「assignment of read-only variable」と報告します。まさにそれが狙いです。間違いはプログラムが実行される前に捕まえられます。

C から持ち込まれる初心者によくある癖が #define MAX_USERS 100 です。避けましょう。マクロは型を持たず、スコープを尊重しない盲目的なテキスト置換なので、デバッガで調べることができず、わかりにくいエラーメッセージを生みます。const(または constexpr)変数は、他のすべてと同じように型チェックされ、スコープを持ちます。

const と constexpr

どちらのキーワードも変更できない値を与えてくれますが、答えている問いは異なります。const は「これは設定後に決して変わらない」と言います。constexpr はより強く「これはコンパイル時に計算できる」と言い、constexpr であるものはすべて自動的に const でもあります。

経験則として、値が固定のリテラルか、コンパイラが行える計算(配列のサイズ、バッファ長、switch のラベル、テンプレート引数)であるときはいつでも constexpr に手を伸ばしましょう。値が実行時に決まるがその後は変わるべきでないとき(たとえば関数引数の const コピーなど)は素の const を使います。

C++20 以降には、必ずコンパイル時に実行されなければならない関数に使う consteval もあります。

consteval int square(int x) { return x * x; }
constexpr int area = square(8); // コンパイル中に計算される

constexpr 関数はコンパイル時に実行されてもよいのに対し、consteval 関数は常に実行されなければならず、そうでなければエラーになります。

ポインタと const: 右から左へ読む

ここが const で人がつまずくところです。キーワードは * のどちら側にも置くことができ、その 2 つの意味は正反対だからです。コツは宣言を右から左へ読むことです。

int* const p2 を右から左へ読みます:「p2 は int への const ポインタ」。const int* p1 は「p1 は const int へのポインタ」と読みます。これを取り違えると、変更できると思っていたものを変更できないと告げるエラーに戸惑い、実際に時間を浪費することになります。

実用的な落とし穴: const のアドレスを取得し、const をキャストで取り除いて元のオブジェクトを変更することは決してしてはいけません。元のオブジェクトが本当に const だった場合、それを行うのは未定義動作であり、コンパイラは値が決して変わらないと仮定してかまいません。あなたの「書き込み」は単に無視されるかもしれません。

関数パラメータとしての const 参照

const の最も一般的な日常的な用途は、大きなオブジェクトをコピーせずに参照で渡すことです。const& パラメータはコピーを避けると同時に、関数が呼び出し側の引数を変更しないことを約束します。

const& での受け渡しは、数バイトより大きいあらゆるパラメータ(文字列、ベクタ、自作クラス)に対する既定の選択です。これにより関数が "Grace" のような一時オブジェクトを受け取れるようにもなる点に注意してください。素の非 const 参照は一時オブジェクトに束縛できないので、ここで const を外すとその 2 回目の呼び出しは拒否されてしまいます。

const メンバ関数

クラスを書くときは、オブジェクトを変更しないメソッドには末尾に const を付けましょう。これにより、そのメソッドが const インスタンスや const& パラメータ上で呼び出せるようになります。これがないと、const ハンドル越しに自分のオブジェクトを読むことすらできません。

読み取り専用のメソッドを const と印付ける規律は const correctness と呼ばれます。早いうちに正しくやりましょう。const であるべきだったメソッドは簡単に追加できますが、後から大きなコードベースに const を後付けするのは苦痛です。const 参照を通したすべての呼び出し側がそれに依存するからです。

次: 演算子

値を const でしっかり固定できるようになったので、次のステップはそれらを使って何かをすることです。演算子のページでは、算術演算子、比較演算子、論理演算子、代入演算子を扱います。整数除算にまつわる落とし穴、演算子の優先順位、そして使うことを許されていない代入演算子と const がどう関係するかも含めて解説します。

よくある質問

C++ における const と constexpr の違いは何ですか?

const は初期化後に値を変更できないことを意味しますが、その値は実行時に計算されてもかまいません。constexpr はより強力で、値がコンパイル時に計算できることを保証します。そのため、コンパイル時定数が必要な場所(配列のサイズ、テンプレート引数、switch のラベル)で使えます。すべての constexpr オブジェクトは const でもありますが、すべての const オブジェクトが constexpr であるわけではありません。

C++ で定数を宣言するにはどうすればよいですか?

型の前に const を置いて値を与えます: const int maxUsers = 100;const 変数は宣言時に初期化しなければなりません。あとから代入することは決してできないからです。コンパイル時定数には constexpr int maxUsers = 100; を選びましょう。型を持たずスコープを無視する、古い C スタイルの #define マクロは避けてください。

C++ における const ポインタとは何を意味しますか?

const がどこに置かれているかによります。const int* pconst へのポインタ で、p の指す先は変えられますが *p は変更できません。int* const pconst ポインタ で、*p は変更できますが p の指す先は変えられません。宣言は右から左へ読みます: int* const は「int への const ポインタ」です。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める