const 修飾子とは
「const」は「constant」の省略形で、「定数」という意味だ。値が変化しないこと、つまり「変更不可」を意味する。
下記の様に変数宣言または関数宣言時に「const」を付加することで、付加された変数または関数が定数であることを宣言する。
const int var = 123; const char *ptr = 0; class Hoge { void func() const; };const 修飾子を付加することで、変数・引数・関数などに変更不可という制限を課すことで、ソースコードの安全性を向上させることが出来る。 と言っても何のことかすぐにはすぐにはピンと来ないかもしれないが、本稿を読み進めば理解できるようになるはずだ。
本稿では、それぞれの種類について具体的に解説し、お約束の演習問題も用意している。 理解しづらい概念も、手を動かして演習問題を解いていけば誰でもマスターできるものなので、ちゃんと演習問題をクリアーしてほしい。
const 変数宣言
変数なのに定数とはこれいかに、ではあるが、下記のように、ローカル変数・グローバル変数宣言に const を指定すると、変数が変更禁止になる。
const double PAI = 3.1415926535; // double 型円周率定数を宣言 int main() { const int N = 100; // 値100の int 型定数を宣言 for(int i = 0; i < N; ++i) { ..... } ..... return 0; }
const 変数に、無理やり代入したり、インクリメント・デクリメントしようとすると、コンパイル時にエラーとなる。
要するにシンボルに定数を割り当て、ソースコードの文書性・メンテナンス性を向上させようということだ。
const は、通常型の前に記述するが、型の直後に書いてもよい。
double const PAI = 3.1415926535;
C/C++ で定数を宣言するには、この方法以外にも下記の様に #define を使用する方法、enum を使用する方法がある。
#define PAI 3.1415926535 enum { IPAI = 3, N0 = 0, N1, // enum はシーケンシャルな値を生成することが可能 N2, };
#define と比べて const 変数は、
- スコープが有効
- 型を持つ
- クラスオブジェクトも宣言可能
という利点がある。
enum と比べた場合、
- 整数以外の定数も可能
- 型を持つ
- クラスオブジェクトも宣言可能
という利点がある。
enum はシーケンシャルな番号を生成可能という利点がある。
何かのIDを連番で生成したい場合は、const int または #define よりも enum を使うのが便利だ。
演習問題:解答例
- const int 変数を宣言し、その変数に値を代入するとコンパイルエラーとなることを確認しなさい。
- { } が入れ子になっていれば、同じ変数名の const 変数を宣言しても問題無いことを確認しなさい。
const ポインタ・参照
以下のように、ポインタ宣言に const を指定すると、ポインタが指す先のデータを変更不可となる。
char v[] = {1, 2, 3}; const char * ptr = &v[0]; // const なポインタを宣言・初期化 *ptr = 0; // コンパイルエラーとなる
const を記述する位置は * の直前でもよい。
char const * ptr = &v[0];
コーディング規約次第だが、型の前に const を書く方がポピュラーだと思う。
ポインタが指す先のデータを変更不可なだけで、ポインタを移動することは可能。
const char * ptr = &v[0]; // const なポインタを宣言・初期化 ptr = &v[1]; // ポインタの再初期化はOK ++ptr; // ポインタの移動もOK
const ポインタが、構造体またはクラスオブジェクトを指す時、public であってもメンバ変数を変更することは出来ない。
struct Hoge { int m_a; }; Hoge hoge; const Hoge *ptr = &hoge; ptr->m_a = 123; // コンパイルエラーとなる
const メンバ関数 で詳しく説明するが、ポインタがオブジェクトを指す時、const ポインタ、参照は const メンバ関数のみコールすることが出来る。
演習問題:(解答例は省略)
- const ポインタに対して代入を行うとエラーになることを確認しなさい。
- int char * const ptr = &v[0]; と宣言してもエラーにならないことを確認しなさい。
- 上記の ptr に対して、*ptr = 123; と書いてもエラーにならないことを確認しなさい。
- int char * const ptr はどういう意味なのか考え、確かめなさい。
const キャスト
場合によっては const ポインタを非const ポインタに、またはその逆を行いたい場合がある。その場合 static_cast<型>() 、 または古いキャスト形式を使用する。
Hoge hoge; const Hoge *ptr = &hge; Hoge *ptr2 = const_cast<Hoge*>(ptr); // const ポインタを非const ポインタにキャスト(変換) //または Hoge *ptr2 = (Hoge *)ptr; // const ポインタを非const ポインタにキャスト(変換)
一般的には古いキャスト形式ではなく、const_cast<>() を使用することが推奨されている。
が、正直に告白します。const_cast<>() はなんだから書くのが面倒なので、筆者は古い形式のキャストを今だに使ってます。
良い子は筆者の真似をしないようにしましょう。
const 引数
引数に const を指定できる。特に、ポインタまたは参照引数の場合、関数内でその参照先が変更されないことを保証する。
void hoge(const Hoge *ptr, const Hoge &r) { ptr->m_a = 123; // コンパイルエラー r.m_a = 456; // コンパイルエラー }
const メンバ変数
クラス宣言時に、メンバ変数宣言に const を指定出来る。
const を指定したメンバ変数は変更することが出来ない。
class Hoge { ..... private: const int m_var; public: void func() { m_var = 123; // const メンバ変数への代入はコンパイルエラー } };
class Hoge { public: Hoge() : m_var(123) // const メンバ変数の初期化 { } Hoge(int var) : m_var(var) // const メンバ変数の初期化 { } ..... private: const int m_var; };
class Hoge { ..... private: const int m_var = 123; // C++11 以上であれば、初期値を指定可能 };
const メンバ関数
- 下記のように、メンバ関数宣言の最後に const を指定すると、const 関数となり、メンバ変数を変更不可となる。
class Hoge { public: int func() const // const メンバ関数 { m_a = 1; // コンパイルエラー } private: int m_a; // メンバ変数 };
class Hoge { public: int func() const // const メンバ関数 int foo(); // 非const メンバ関数 ..... }; int main() { Hoge hoge; const Hoge *ptr = &hoge; ptr->foo(); // const ポインタは、非const メンバ関数をコール出来ない ptr->func(); // const メンバ関数であればコール可能 }
参考
演習問題解答例
- const int 変数を宣言し、その変数に値を代入するとコンパイルエラーとなることを確認しなさい。
- { } が入れ子になっていれば、同じ変数名の const 変数を宣言しても問題無いことを確認しなさい。
int main() { const int N = 100; N = 200; }
int main() { const int N = 100; cout << N << "\n"; { const int N = 200; cout << N << "\n"; } cout << N << "\n"; }