C++のconstメンバ関数ってなんとなく「そのメンバ関数の呼び出しによってオブジェクトの内容を変更しないことを宣言する」程度に理解していて、オブジェクトの内容を変更しないというのが具体的にどういう操作なのかきちんと理解していませんでした。
そのため、例えばなぜこれがエラーで:
class Hoe {
public:
Hoe() {
}
virtual ~Hoe() {
}
void func() {
...
}
};
class FugaNG {
private:
Hoe _hoe;
public:
FugaNG() : _hoe() {
}
virtual ~FugaNG() {
}
void callHoeFunc() const {
_hoe.func(); // ← コンパイルエラー!
}
};
これがエラーにならない:
class FugaOK {
private:
Hoe *_pHoe;
public:
FugaOK() {
_pHoe = new Hoe();
}
virtual ~FugaOK() {
}
void callHoeFunc() const {
_pHoe->func(); // ← エラーにならない
}
};
のか、知識としては分かっていても理屈が分かっていませんでした。
で、いろいろ調べたところ、以下のように考えるのが一番直感的だということが分かりました。(C++に詳しい人にとっては当たり前のことなのかもしれませんが…。)
「constメンバ関数では、暗黙的に渡されるthisポインタがconstで修飾される」
具体的に見ていきます。
前提
メンバ関数が呼び出される時、自分自身へのポインタであるthisが暗黙的に渡されるわけですが、例えば Hoe#func()
についてこれを便宜上次のように書くことにします:
void func([Hoe *this]) {
...
}
オブジェクト経由でのメンバ関数を呼び出すケース
FugaNG#callHoeFunc()
について考えてみます。このメンバ関数はconstメンバ関数ですので、渡されるthisポインタは次のようにconstで修飾されます:
void callHoeFunc([const FugaNG *this]) {
_hoe.func();
}
ここで、_hoe.func()
は実は this->_hoe.func()
で、さらに上記の表記法で書くとこの呼び出しは this->_hoe.func([&this->_hoe])
となりますが、thisがconst修飾されていることでconst修飾が波及して &this->_hoe
もconst修飾された const Hoe *
になるため、呼び出そうとしているメンバ関数のシグネチャは
void func([const Hoe *this])
である必要があります。ところが実際は func()
は非constなメンバ関数であるためシグネチャは
void func([Hoe *this])
となり、一致せずエラーになるというわけです。
ポインタ経由でのメンバ関数を呼び出すケース
一方、FugaOK#callHoeFunc()
についてはどうでしょう。このメンバ関数も同じくconstメンバ関数ですので、渡されるthisポインタは次のようにconstで修飾されます:
void callHoeFunc([const FugaOK *this]) {
_pHoe->func();
}
ここで、_pHoe->func()
は実は this->_pHoe->func()
で、さらに上記の表記法で書くとこの呼び出しは this->_pHoe->func([this->_pHoe])
となります。
さて this->pHoe
の型はどうなるでしょう。thisがconst修飾されていることで this->_pHoe
もconst修飾されてconst Hoe *
になると思いますか?実はそうはなりません。const参照はされますが、Hoe * const
のように修飾されます。これは、こうしないとthisが指すオブジェクトの中身が保護できないからです。
仮に this->_pHoe
が const Hoe *
のように修飾されてしまうと、_pHoe
が指す先は保護されますが _pHoe
自体は変更可能ということになってしまい、目的が達せられないことになります。_pHoe
自身を保護するために Hoe * const
のように修飾されるというわけです。
続けましょう。
Hoe * const
の const
は関数のシグネチャには影響しませんので、呼び出そうとしているメンバ関数 func()
のシグネチャは
void func([Hoe *this])
となります。このため無事 Hoe#func()
が呼び出せるというわけです。
まとめ
constメンバ関数の挙動を考える時は、次の原則を頭に置いておけば分かりやすいです。
- constメンバ関数では、暗黙的に渡されるthisポインタがconstで修飾される
- const修飾は波及する
「全然直感的じゃないじゃん!」と思われた方、すみません…。