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

実験結果

  1. constオブジェクトはconstメソッドを、非constオブジェクトは非constメソッドを呼び出している
  2. 非constメソッドを消し、constメソッドだけを残すとどうなるか?
    1. 非constオブジェクトもconstオブジェクトも、constメソッドを呼び出すようになる
  3. constメソッドを消し、非constメソッドだけを残すとどうなるか?
    1. 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ポインタから値を読み出し、返しているだけで書き換えは行っていないからだ。

規格で定められている、というだけの理由なのだろうか。ううむ。