Arantium Maestum

プログラミング、囲碁、読書の話題

Effective C++勉強メモ: Item 3 constだ。constを使うんだ

constできるところは全部constで。というお話。

それなら変数全部デフォルトでconstだったらよかったのでは?と一瞬思ったがrust面に落ちかけているな。(どちらかというとC++がダークサイドか)

この章の話題は主に二つ。

  • ポインタやイテレータのconst化に関するあれこれ
  • メンバー関数のconstとnon-constでのオーバーローディング

ポインタに関しては単純

T t; //ただのT型の変数t宣言

T* t; //T型へのポインタtを宣言

ここからconstをつけていく。のだがその前に言っておきたい。

不満

T* t; //T型へのポインタtを宣言

T *t; //ポインタtが指し示すものの型はT

どう考えても後者は不自然な気がするのだが(「私は今tを宣言してるんだ、*tじゃない!」と言いたくなる)、T* t, u, v;とかの落とし穴(uとvはポインタじゃなくただのT型の変数になる)のせいで後者に統一した方がいいので結構もにょる。これは私だけだろうか?

閑話休題

ポインタとイテレータ

Effective C++に戻ると

const T* t; //(const T)型へのポインタtを宣言(1)

T const * t; //上に同じ(1)

T* const t; //T型へのconstポインタtを宣言(2)

const T* const t; //(const T)型へのconstポインタtを宣言(3)

const vector<T>::iterator t; //T型の要素を含むvectorへのイテレータtをconstとして宣言(4)

vector<T>::const_iterator t; //T型の要素を含むvectorへのconstイテレータtを宣言(5)

(1)の例が二つとも同じ意味なのは微妙だなーと思いつつ。const T*T* constだとTがconstなのか(ポインタが指し示す先のデータの内容を変更できない)、T*がconstなのか(ポインタ自体が指し示すアドレスを違うアドレスに変えることができない)が比較的わかりやすいかもしれない。T const*だと絶対「どっちだ?」と思いそう。

しかしポインタとイテレータだとconstのつけ方・読み方が変わってしまっている。const T* t(1)はconst Tへのポインタなのに対して、const vector<T>::iterator tはconstなイテレータだ(つまり他のものを指し示すようにできないイテレータ)。というかそんなイテレータ使う状況ってあるのかな?vector<T>::const_iterator tをできるだけ使おう。

と、ここら辺まではC++の常識。

constメンバー関数

constメンバー関数の話題から面白くなってくる。

class T {
  public:
  const S& operator[](const size_t pos) const {
    //処理
  }
  S& operator[](const size_t pos) const {
    //処理
  }
};

Sを要素とするコンテナクラスTの[]を、const Tでも使えるものとそうでないもので別々に定義。後者は例えばt[0] = s;のような代入にも使えるが、前者はできない。

constかそうでないかの違いだけでオーバーロードできるのは、考えてみれば当然なんだけど、意外性はあるかもしれない。

このオーバーロードされた二つの関数、処理の内容は大抵同じなことが多い。だとすると、特に処理が長い場合、片方をもう片方を使って定義したくなる。

class T {
  public:
  const S& operator[](const size_t pos) const {
    //処理
  }
  S& operator[](const size_t pos) const {
    return const_cast<S&>(static_cast<const T&>(*this)[pos]);
  }
};

非constのメンバー関数をconst版を使って定義する、というのは当然。const関数の中でnon-const関数を使うのが危険なのはわかりやすいと思う。ただ、「constオブジェクトからもoperator[]が利用できるようにしたい」という順番で考えているとハマる可能性はある。

static_cast<const T&>(*this)[pos]でまずはオブジェクトをconstにキャストしてからを使うことでconst版のoperatorを呼び出す。その結果をconst_cast<S&>でnon-constにキャストし直す、というもの。

ただ読んでいた時は「え、うん、あ、おう・・・?」という印象だったのだが、記事に書いてみたらなんだか自明な気がしてきた。ブログは勉強に効く。