constキーワードをつけることで特定オブジェクトについて「変更してはいけない」制約をはっきりさせ、コンパイラにその制約を守っているかのチェックをサポートしてもらうことができる。また、コンパイラの最適化が働きやすくなるメリットもある。
定数宣言
変数宣言時にconstをつけることで変更しないこと、つまり定数として扱うことを明示できる。
const int kConstant = 1;
ポインタに対しては少し複雑で
- constが*の左にある時は「ポインタが指し示すデータが不変」
- constが*の右にある時は「ポインタそのものが不変」
- constが*の左右にある時は「データもポインタも不変」
を意味する。具体的には
int main(){ char test[] = "test"; char const * p = test; // p[0] = 'T'; // error p++; // ok char * const q = test; q[0] = 'T'; // ok // q++; // error }
となる。
関数宣言
関数宣言においては
- 仮引数
- 戻り値
- メンバ関数には関数そのもの
にconstをつけることができる。
戻り値にconstをつけるべきシーンとしては
class Rational{...}; const Rational operator*(const Rational &lhs, const Rational &rhs);
と宣言しておくことで
Rational a, b, c; if(a * b = c) // a * b == cのつもりだった
というミスをエラーとして検出することができる。
constなメンバ関数
メンバ関数にconstをつけるメリットは2つ。
- どのメンバ関数がオブジェクトを変更するのかの区別をつけやすくなる
- constなオブジェクトを扱える
なお、メンバ関数のconstは「ビットレベルの不変性」を意味し、「論理的な不変性」を意味するものではない。(コンパイラでのチェックを容易にするため)
例えば、保持しているポインタを返すメンバ変数はその動作のみではオブジェクトを変更するわけではないのでビットレベルの不変性を守っておりconst宣言ができるが、返したポインタを介してオブジェクトの内容を変更される可能性があり「論理的な不変性」を持っているとは言えない。C++では「論理的な不変性」を保証する責任はプログラマにある。
一方で、「論理的な不変性」は保ちつつも「ビットレベルの不変性」が崩れるケースもある。例えばthread safeにするためのmutexは論理的な内容に影響を及ぼさないがビットレベルでは変更が生じるためconst宣言ができない。この場合は、宣言時にmutableキーワードをつけるとconstメンバ関数でも変更できるようになる。
参考文献
Effective C++ 第3版, スコット・メイヤーズ著