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がつけられなくなるというのも理不尽な気がします。
結局問題はコンパイラーが実装の検査をしているにすぎないところにあるんですね。 キャッシングのための代入文も、本当にオブジェクトの状態を変える代入文も、 区別がつかないのです。 もちろんコンパイラーにプログラムの「意図」を汲み取れという訳には いきませんし、実装の検査以上に余計なことをされてはもっと困ってしまいます。
最近のコンパイラーにはこういう問題を回避するためのしかけがあります。 それが更に面倒を引き起こしたりもするのですが、この辺の話はまた次回。