C++のconst宣言の対象、効果、効果範囲、その他の注意点

const宣言は、宣言されたデータを変更できないデータ、つまり定数にします。
C++のconst宣言の対象、効果、効果範囲、その他注意点について整理したいと思います。

const宣言の対象データ

まず、const宣言が付けられるデータ型は何かを整理したいと思います。

C++のデータ型

C++はデータの取扱方法として、
->ポインタを通して扱う
->参照を通して扱う
->データ実体(レコード)を直接扱う
の3通りの方法を持っています。
そして、データ実体には、基本データ型、データセット(クラス、構造体)、配列、共用体、enum、関数(メソッド)などがあります。

const宣言の対象となるデータ、ならないデータ

const宣言は、変更できるデータを変更できないデータにするため、そもそも変更できないデータは対象になりません。

変更できないデータ:
->関数(ただし、メソッドにはオブジェクトの変更不可という目的でconst宣言が付く。)
->参照自身(参照自身は変更できない。参照先のデータ実体については変更できる。)

変更できるデータ:
->ポインタ自身
->ポインタ先のデータ実体
->参照先のデータ実体
->データ実体
->->基本データ型
->->データセット
->->その他

以上の変更できるデータがconst宣言の対象になります。
ちなみに、参照自身が変更できないことについての詳解は、C++の参照の宣言方法、振る舞い、一時オブジェクトによる初期化をご覧ください。

const宣言の効果と効果範囲

次に、const宣言の効果と効果範囲を対象データごとに考えます。

ポインタ自身

DataName * const VariableName = AnyPointer;

ポインタは、ポイントするレコードのアドレスであり、アドレスVariableNameの前にconstを付けるとアドレスVariableName自身が変更できなくなります。この宣言の場合、ポイント先のデータ実体は変更できます。

ポインタ先のデータ実体

const DataName *VariableName = AnyPointer;

デリファレンスされたDataName型のレコードにconstが付けられています。よって、ポイント先のデータ実体をこのポインタを通して変更できなくなります。この宣言の場合、ポインタそのものは変更できます。後ほど詳解しますが、このポインタを通さなければデータ実体の変更はできます。

参照先のデータ実体

const DataName &VariableName = AnyData;

この参照を通してデータ実体を変更できなくなります。次に詳解しますが、この参照を通さなければデータ実体の変更はできます。

ポインタ先のデータ実体と参照先のデータ実体の補足

ポインタ先のデータ実体と参照先のデータ実体の注意点として、const宣言したポインタや参照を通さなければデータ実体を変更することはできて、それがconst宣言されたポインタや参照にも反映されます。
例:

*AnyPointer = AnyData1;
const DataName *VariableName = AnyPointer;
*AnyPointer = AnyData2;
if(*VariableName == AnyData2) //true

つまり、const宣言したポインタや参照を通してデータ実体を変更することはできませんが、データ実体それ自体は定数にはならず変更できます。さらに、ポインタや参照はコピーしたデータ実体をポイント・参照しているのではなく、直接データ実体をポイント・参照しているのでデータ実体の変更がポインタや参照にも反映されます。
「ポイント先のデータ実体が定数になる」、「参照先のデータ実体が定数になる」ではなく、「ポインタを通してデータ実体を変更できなくなる」、「参照を通してデータ実体を変更できなくなる」ということです。

基本データ型

const DataName VariableName = Data;

データ実体VariableNameを変更できなくなります。ちなみに、VariableNameはDataと異なるデータとして初期化されるので、後からDataを変更してもVariableNameに変更が反映されることはありません。

データセット

const ClassName ObjectName;
const ClassName ObjectName(AnyArguments);

データ実体ObjectNameを変更できなくなります。データセット(クラス、構造体)は、メンバで構成されるデータ集合なので実際にはデータ実体である各メンバを変更できなくなります。メンバにはポインタ、参照、データ実体が入りますが、ObjectNameのデータ実体である
->ポインタ自身
->参照自身(そもそも変更できないデータ)
->データ実体
が変更できなくなります。

一方で、
->ポインタ先のデータ実体
->参照先のデータ実体
は変更できます。(このポインタや参照を通してデータ実体を変更することができます。)

さらに、オブジェクトメンバにオブジェクトがあるような場合は、上記のルールがデータ実体について再帰的にあてはまります。

メソッドのconst宣言

class ClassName{
 DataName Method() const {}
};

1. Methodを通してClassNameのデータ実体を変更できなくなります。
2. ClassNameのデータ実体のポインタや参照をconstなしに返すことができなくなります。
3. Methodからconstが付いていない他のメソッドを呼び出せなくなります。
一方で、ClassNameのオブジェクトをconstで作成した場合に、constが付いていない他のメソッドは呼び出せませんが、const宣言したMethodは呼び出せるという効果が生じます。これにより安全なクラス設計を行えます。

以上、const宣言の効果と効果範囲を考えてきました。

その他の注意点

初期化なしのconst宣言はできない

初期化なしのconst宣言はできません。
基本データ型の例:

const DataName VariableName = Data; //OK
const DataName VariableName; //エラー

const宣言を緩めることはできない

ポインタや参照を使って、変更できないデータを変更できるデータに制限を緩めることはできません。
例:

const DataName VariableName = Data;
DataName *PointerName = &VariableName; //エラー
const DataName *PointerName = &VariableName; //OK

ちなみに、=でデータを代入する場合や、関数の引数・返値でデータを引き渡す場合に、データを「コピー」することはできます。

一時オブジェクトによる参照の初期化

通常、一時オブジェクトで参照の初期化をすることはできませんが、const宣言を付することで一時オブジェクトでも参照を初期化することができます。

int &VariableName = 123; //エラー
const int &VariableName = 123; //OK

詳しくは、C++の参照の宣言方法、振る舞い、一時オブジェクトによる初期化をご覧ください。

参考サイト

その2 constのあれこれ, ○×(まるぺけ)つくろーどっとコム, since 2005/5/13
その17 constのあれこれ2, ○×(まるぺけ)つくろーどっとコム, since 2005/5/13
C++の基礎 : const 修飾子, ソフィア・クレイドル, 2002-2014