const宣言したいのに、コンパイラーがそうさせてくれない ことがあります。これが困る。
よくあるのは、使いたいライブラリーに適切なconstがついていない場合。 例えば、
void Hoge::Open(const char * filePath) { ::LibOpen(filePath); // 以下省略 }
という具合に、誰かが作ったライブラリーの ::LibOpen() を使って処理するとします。 引数の filePath は定数である(つまり、Hoge::Open() の中で変化しない)と いうことで、constを付けています。 ところが、LibOpen() の第1引数にconstがついていないと、これはエラーになります。
Hoge::Open() の引数からconstを取ればコンパイラーに文句を言われないのは わかっている。でもこういう場合、このライブラリーの函数はことごとく constなしで作られていたりします。ということは、あちこちで本当はつけたい constを削らなければならなくなり、constの効用が活かされません。
困ったので
void Hoge::Open(const char * filePath) { char * path = ::strdup(filePath); ::LibOpen(path); // 以下省略 }
としてみたら、今度は strdup() の引数にconstがなくて、こちらがエラーになった。
void Hoge::Open(const char * filePath) { char * path = ::malloc(strlen(filePath) + 1); ::strcpy(path, filePath); ::LibOpen(path); // 以下省略 }
も同様の理由で不可。結局、string.h や memory.h の函数を一切使わず、
void Hoge::Open(const char * filePath) { char path[MAX_PATH + 1]; int i; for (i = 0; filePath[i] != '\0'; i++) { path[i] = filePath[i]; } path[i] = '\0'; ::LibOpen(path); // 以下省略 }
みたいなことになりました。
あ、最近はstring.hやmemory.hにはちゃんとconstがついているのが多いので、 ここまでトホホな状況にはなりませんけど。
似た例としては、ライブラリーの方で
typedef struct { char * path; // 省略 } PARAM; extern void LibOpen(PARAM * param);
みたいに宣言されていて、
void Hoge::Open(const char * filePath) { PARAM param; param.path = filePath; ::LibOpen(¶m); // 以下省略 }
というように使いたい場合。これは代入文でエラーが出てしまいますから、 エラーの意味が前よりもわかりにくくなります。
ライブラリーを使わず、自分ですべて書いていても困ることがあります。 例えば、最初こんな函数を作りました。
== hoge.h == class Hoge { public: virtual int GetMunya(void) const; }; == hoge.cpp == int Hoge::GetMunya(void) const { int munya = /* いろいろ計算 */; return munya; }
ところが、munyaの「いろいろ計算」の中身は本当にいろいろで、とても時間がかかる。 しかもHoge::GetMunya()はしょっちゅう使われる函数だということで、こんな風に 改良してみます。
== hoge.h == class Hoge { public: virtual int GetMunya(void) const; private: boolean MunyaCalced; int Munya; }; == hoge.cpp == int Hoge::GetMunya(void) const { if (! MunyaCalced) { Munya = /* いろいろ計算 */; MunyaCalced = true; } return Munya; }
もちろん、MunyaCalcedは構築子あたりでfalseに初期化されていると思ってください。 こうすると、時間がかかる「いろいろ計算」は1回だけで済みます。
問題は、Hoge::GetMunya()で変数MunyaやMunyaCalcedが更新されるため、 constがついているとエラーになってしまうこと。 でも、実装が変わったからconstがつけられなくなるというのも理不尽な気がします。
結局問題はコンパイラーが実装の検査をしているにすぎないところにあるんですね。 キャッシングのための代入文も、本当にオブジェクトの状態を変える代入文も、 区別がつかないのです。 もちろんコンパイラーにプログラムの「意図」を汲み取れという訳には いきませんし、実装の検査以上に余計なことをされてはもっと困ってしまいます。
最近のコンパイラーにはこういう問題を回避するためのしかけがあります。 それが更に面倒を引き起こしたりもするのですが、この辺の話はまた次回。