C++のconstは別のメソッドを作る
動機
何気なくconstオブジェクトを使っていたら、アクセサメソッドで「const修飾子が無視されてますよ」とエラーが出てコンパイル出来なくなった。const参照で渡しても同じ。
恐らく基本的なところが理解できていないせいで遭遇したエラーなので、未来の自分に向けてメモ。
実験コード
#include <iostream> #include <cstdlib> namespace { class Foo { private: double d_data ; public: explicit Foo(const double data) : d_data(data) {} virtual ~Foo() {} public: double get_data() { return this->d_data ; } void print_data() { std::cout << d_data << "\n" ; } double get_data() const { return this->d_data + 1.0 ; } void print_data() const { std::cout << "CONST: " << d_data << "\n" ; } }; } int main() { Foo foo(1.25) ; const Foo const_foo(1.75) ; std::cout << foo.get_data() << "\n" ; foo.print_data() ; std::cout << const_foo.get_data() << "\n" ; const_foo.print_data() ; return EXIT_SUCCESS ; }
デストラクタにvirtualがついてるのはいつもの癖です。無い方が良いというのは分かっているのですが何となく。
実行結果
$ g++ test.cpp -o test -Wall -Wextra $ ./test 1.25 1.25 2.75 CONST: 1.75
実験結果
- constオブジェクトはconstメソッドを、非constオブジェクトは非constメソッドを呼び出している
- 非constメソッドを消し、constメソッドだけを残すとどうなるか?
- 非constオブジェクトもconstオブジェクトも、constメソッドを呼び出すようになる
- constメソッドを消し、非constメソッドだけを残すとどうなるか?
- constオブジェクトが非constメソッドを呼び出すところでコンパイルエラーが起きる
結論
constオブジェクトはconstメソッドしか呼び出せず、かつconstメソッドを優先的に呼び出す。従って、
- const妥当なメソッドは可能な限りconstメソッドとして宣言・定義する
- 同じ名前のconstメソッドと非constメソッドが存在する場合、動作の一貫性に注意する
- コードジェネレータを使えばDRYかつ一貫したコードになるだろう
ということが言える。
考察
結果1は
- constメソッドと非constメソッドは別のメソッドであり、単にオーバーロードされているだけである
- constオブジェクトはconstメソッドを、非constオブジェクトは非constメソッドを呼び出す
ということを示している。問題は、これがどのような仕組みで行われているのか?ということだ。
結果2-1、結果3-1から非constオブジェクトでもconstメソッドを呼び出すことができるが、その逆は成り立たないことが分かる。C++の基礎 : const 修飾子の「constメンバ関数」の項目を見ても、「constオブジェクトに対して呼び出せるメソッドは、constメンバ関数だけです」とある。これはconst修飾子の性質そのものだ。だから、何らかの形でconst修飾子が関わっていると推測できる。
http://www.sun-inet.or.jp/~yaneurao/yaneSDK3rd/chap0108.htmlによれば、constメソッドの呼び出しに対しては、thisポインタの型が通常の「T* const」から「const T* const」に切り替わるらしい。これが関わっているのだろうか?しかしこれでは、上のFoo::get_data()のような単純なアクセサメソッドも拒絶される理由が分からない。単にconstポインタから値を読み出し、返しているだけで書き換えは行っていないからだ。
規格で定められている、というだけの理由なのだろうか。ううむ。