本記事は「ポインタ/参照とconstキーワードについて、なんとなく分かってきたかも?」という学習ステージの方が、const修飾の役割を イメージできる ような理解を目指しています。

図解(凡例)

int val = 123;    // 普通の変数
int* ptr = &val;  // ポインタ(pointer)
int& ref = val;   // 参照(reference)
const int cval = 45;  // 定数(constant)

本記事では、上記C++ソースコードに対応する図示を次のルールで行います。"白色の箱"が普通の変数(cval)やポインタ型変数(ptr)を、"赤色の箱"が定数(cval)を、"黄色タグ"が参照型変数(ref)をそれぞれ表します。箱の中身は、各変数が保持している値に対応します。ポインタ型変数は"差し先変数のアドレス値"を保持しますが、分かりやすさのため矢印(→)にて表現します。参照型変数についても同様です。1

legend.png

const × ポインタ型

ポインタ型変数の宣言では、アスタリスク(*)の前または後ろconstキーワードを配置できます。つまりconstの有無に応じて4種類のポインタ型変数が存在します。なお先頭アスタリスク(*)よりも前では、const intint constいずれの書き方でも同じポインタ型となります。お好みの順序で利用ください。

int       *       ptr1 = &val;
const int *       ptr2 = &val;
int const *       ptr2 = &val;  // 上に同じ
int       * const ptr3 = &val;
const int * const ptr4 = &val;
int const * const ptr4 = &val;  // 上に同じ

constポインタの図解

それぞれのポインタ型と、const修飾の関係を図示します:

ptr-const1.png

  • アスタリスク(*)の前にconstを置いた場合、ポインタ型変数ptrN介して差し先の値42を書き換えることはできません。(図中では"赤枠"で表現)
  • アスタリスク(*)の後ろにconstを置いた場合、ポインタptrN定数となります。つまり差し先の変数/矢印が向かう先は固定です。

前者では、あくまで “このポインタを経由した” 値の書き換えが禁止されるだけであり、このポインタ経由でなければ値は書き換え可能です。

constポインタと定数の関係

先ほどはポインタの指す先が普通の変数でしたが、ポインタの指す先が定数(const int)の場合は次の関係になります;

int       *       ptr5 = &cval;  // NG: コンパイルエラー!!
const int *       ptr6 = &cval;  // OK
int       * const ptr7 = &cval;  // NG: コンパイルエラー!!
const int * const ptr8 = &cval;  // OK

ptr-const2.png

const × 参照型

参照型変数の宣言では、アンパサンド(&)の前にのみconstキーワードを配置できます。つまりconstの有無に応じて2種類の参照型変数が存在します。ポインタ型と同様にconst intint constは順不同です。2

int       & ref1 = val;
const int & cref1 = val;
int const & cref1 = val;  // 上に同じ

const参照の図解

それぞれの参照型と、const修飾の関係を図示します:

ref-const1.png

図示では省略しましたが、アンパサンド(&)の後ろにconstキーワードを書くことはできません。仮に書けた場合は「参照型変数の参照先は固定」に相当しますが、もともと参照型変数の参照先を変更することは不可能ですから、コンパイルエラーとして扱われます。

int       & const ref = val;   // NG: コンパイルエラー
//          ^^^^^
const int & const cref = val;  // NG: コンパイルエラー
//          ^^^^^

const参照と定数の関係

参照の指す先が定数(const int)の場合は次の関係になります;

int       & cref1 = cval;  // NG: コンパイルエラー
const int & cref2 = cval;  // OK

ref-const2.png

おしまい。


  1. 参照型変数だけが"箱"形をしていないことに気づいたでしょうか?C++言語では、参照型変数と通常変数・ポインタ・定数が明確に区別されることを反映しています。通常変数・ポインタ・定数はメモリ上に実体が存在するため、本文中では"箱"のイメージを用いました。一方の参照型変数はその形状が示す通り、別の実体に対するタグ(tag)/別名(alias)と解釈できます。(厳密な議論はこちらの記事を参照) 

  2. 古き良きC++03時代には、ある型Tに対する参照型は1種類(T &)しか存在しませんでした。C++11以降のモダンなC++言語では、従来からの左辺値参照型(T &)と新しい右辺値参照型(T &&)とで2種類の参照型が存在します。constキーワードも考慮すると、厳密には4種類の参照型(T &,const T &,T &&,const T &&)となるのです。ただし、本記事範囲では右辺値参照型(T &&)へ言及する必要性がないため、昔ながらの左辺値参照型(T &)のみを対象としています。 

1473683070
「なんにも知らないって、すっごくしあわせ!」--スヌーピー