C++によるプログラミング入門補足2
constについて後編
前編ではconstの紹介をしました。constは本当に重要です。これは、うまく使えば、プログラム中に張り巡らされたエラー排除のメカニズムになります。しかし、それがなるほどとわかるまでには、少し知識と経験が必要です。ここでは、本当はしっくりこない人も、とりあえず、
constは重要
ということを認めてください。
初心者には、わかりづらい面もあると思います。ただ、おどすつもりはないのですが、今回の内容を無視していると、ちゃんとしたC++プログラマとして認めてもらえないでしょう。
const宣言してしまえば、その変数は定数になり、したがって変更できなくなる、これによりうっかり変更ミスがなくなる、という話をしました。ではそのわざとらしい例です。
//const_sample4.cpp
#include <iostream>
using namespace std;
int main()
{
int x = 100;
//xという名前の変数が定義され、それが100に初期化されました。
//プラグラマはxは100に固定して、変更しないつもりだったとします。
cout << "x = " << x << endl;
//ここで、いろいろ、、、やったとしましょう。
x = 99; //ありゃりゃ。間違えて、xの値を書き換えるコード書きました。
//その後、xが書き換わったことで恐ろしいエラーがでたりするのです。
//また、いろいろ、、、
}
(警告が出ていますが、それは「xに入れた値を使っていないこと」に対してです。
xを使うコードを書けば消えます。もちろん、エラーにはなっていません。)
そして、プログラマは、原因不明の実行時エラーに腰を抜かすのです。(もちろん、たまたま、エラーの出ないプログラムかもしれませんが、、、。)何のことはない、自分で書き換えているのですから当然です。プログラムが長く複雑になってくると、こういうミスはとてもわかりづらいのです。1000行、2000行、、、10000行のコードを考えてみてください。こんなミスを一個所でもしたら、探し出すのはとても大変そうです。
...なんて、言ったらみなさん、どう思います。初心者の頃の私は鼻で笑っていました。書き換えたくない変数を自分で書き換えるなんてまともな人のやることかね、と。でも、そのうち笑い事でないことがわかりました。そうなんです。信じてください。上の例はあまりにも単純な例です。実際にはとても見つけづらいところで、不本意な書き換えが起こってしまったりするのです。他人と共同作業なら尚更です。
ところが、上のプログラムを
//const_sample5.cpp
#include <iostream>
using namespace std;
int main()
{
const int x = 100;
//xという名前の定数が定義され、それが100に初期化されました。
//プラグラマはxは100に固定して、変更しないつもりだったとします。
cout << "x = " << x << endl;
//ここで、いろいろ、、、やったとしましょう。
x = 99; //ありゃりゃ。間違えて、xの値を書き換えるコード書きました。
//その後、xが書き換わったことで恐ろしいエラーがでたりするのです。
//また、いろいろ、、、
}
(「オブジェクト」と書いてありますが、xのことです。
基本型の変数も「オブジェクト」の一種と考える流儀もあります。)
コンパイル時に発見されるエラーは直すのは簡単です。(最初のプログラムが実行時エラーを起こした場合、恐ろしいのは、その原因がxの書き換えにあるとなかなか気が付かないことです。)
いつものようにくどかったですが、言いたいことはわかってもらえたでしょうか。そうすると、同じ事がクラスのオブジェクト(インスタンス)でも言えるとわかるのではないでしょうか。例えば、
...
class Inu
{
.....
};
void main()
{
Inu Nora("ボス");
.....
}
#include <iostream.h>
class Inu
{
.....
};
void main()
{
const Inu Nora("ボス");
.....
}
//const_sample6.cpp
#include <iostream>
#include <string>
using namespace std;
class Inu
{
string name;
public:
Inu(string);
void set_name(string);
void naku() const;
};
Inu::Inu(string n) : name(n){}
void Inu::set_name(string n){ name = n; }
void Inu::naku() const{ cout << "わんわん、俺は" << name<< "だ。" <<endl; }
int main()
{
const Inu nora("ボス");
nora.naku();
//以下、いろいろ処理があるとします。
}
のようなプログラムが書けます。mainの中の
const Inu nora("ボス");
のconstは、「俺(プログラマ)は、noraの中身変えたくない」という意思表示なのです。nakuは、オブジェクトの中身を変更しないので、このプログラムは問題なく動きます。ところが、半年後くらいに、mainの中に、うっかり
nora.set_name("大ボス");
というコードを書き込んでしまったらどうなるでしょう。すると、コンパイル時にエラーになるのです。
//const_sample7.cpp
#include <iostream>
#include <string>
using namespace std;
class Inu
{
string name;
public:
Inu(string);
void set_name(string);
void naku() const;
};
Inu::Inu(string n) : name(n){}
void Inu::set_name(string n){ name = n; }
void Inu::naku() const{ cout << "わんわん、俺は" << name<< "だ。" <<endl; }
int main()
{
const Inu nora("ボス");
nora.naku();
//以下、いろいろ処理があるとします。
nora.set_name("大ボス"); //オーミステーク!
}
(コンパイル時のエラーになってくれる。ありがたや、ありがたや。)
しかし、コンパイラってのも頭いいですよねー。nakuという関数を呼び出してもオブジェクトのデータは変更されないので問題ない。だから、nora.naku()があってもエラーにしない。しかし、set_name()を実行するとオブジェクトのデータが変更されてしまうので、「それはダメ」とエラーにしてくれる。...どうしてなんでしょうね?
確かにコンパイラは賢いですが、人間(プログラマ)の助けも必要です。私たちは、nora_naku()の後ろにconstを付けておきました。それは、「この関数はオブジェクトのデータを変更しない」という意味だったのです。コンパイラは、関数の後ろについているconstを見て判断していたのです。もうわかってた人は偉いです。
ところで、naku()の定義のときに、後ろにconstを付け忘れることがあります。たとえば、const_sample6.cppで、naku()の後ろのconstを削除すると、コンパイル時のエラーになってしまいます。
//const_sample8.cpp
//エラー!!!
#include <iostream>
#include <string>
using namespace std;
class Inu
{
string name;
public:
Inu(string);
void set_name(string);
void naku(); //const忘れた
};
Inu::Inu(string n) : name(n){}
void Inu::set_name(string n){ name = n; }
void Inu::naku(){ cout << "わんわん、俺は" << name<< "だ。" <<endl; } //const忘れた
int main()
{
const Inu nora("ボス");
nora.naku();
//以下、いろいろ処理があるとします。
}
(noraに対してnakuを呼んでもエラーになる。)
ここで、ああ、面倒だ。じゃ、「const Inu nora("ボス");」のconstも取ってやれ、と思う人がいるようです。すると、コンパイル時のエラーはなくなります。そもそもconstなんか使わなければ、こんなエラーはないのです。しかし、それは...絶対にやめましょう。つまり、ちゃんと、constを付けましょう。
constは、「不注意によるデータの書き換え」を防ぐために重要です。これを張り巡らせることで、プログラマの寿命を削る「実行時の不思議なエラー」を、初心者でも直せる「コンパイル時のエラー」に格下げすることができるのです。
constをきちんと付けない人と協力してプログラムを書く場合...というか、そういう人と協力してプログラムを書くことは、不可能に近いと思います。「初心者のうちはいいや」と思わず、ぜひ、習慣にしましょう。つまり、
オブジェクトの内容を変えないメンバ関数には、よほど特別な理由がない限り、const宣言すべき
なのです。