2014年7月16日水曜日

【読書ノート】Effective Modern C++ - Item 1 [1 / 2]

O'Reilly Early Releaseで購入したEffective Modern C++のドラフト版の読書記録です。

※この記事はあくまで私の備忘録であり、本の内容のまとめではないことに注意してください!(断りなく自分の解釈や考え・感想を入れたり、理由もなく内容を省略したり、間違いや曲解もあると思います。誤りの指摘は歓迎です。)

Chapter 1では型推定、Item 1ではテンプレートの型推定が出てきました。直感的に使えますが、ちゃんとしたルールは知らなかったので勉強になりました。また、Universal Referenceという用語は初耳で、挙動も知らなかったのでこちらも勉強に。

Chapter 1 型推定

C++98から関数テンプレートでは型推定が行われていた。
C++11ではautoとdecltypeが加わり、C++14ではその2つを使用できる文脈が増えた。
型推定は、型を変更する時に1箇所変えるだけで他の部分にも伝播するため、手で複数箇所を変更する手間から解放してくれる。
この章は型推定の基本的な情報を提供する。

Item1: テンプレートの型推定を理解しよう

テンプレートの型推定は、詳細な仕組みを知らなくても直感的に使える優れたシステムの一例。
autoで宣言された変数の型推定は、テンプレートの型推定と本質的には同じ。
しかし、仕組みを理解していないと面食らうこともあるだろう。
template <typename T>
void f(const T& param); // パラメータの型はconst T&

int x = 0;
f(x); // 引数型はint
ここではTはint型と推定されているが、パラメータの型はconst int&と推定されている
※復習: paramはパラメータ、xは引数(パラメータと引数の区別)

Case 1: パラメータの型がポインタ/参照だが、Universal Referenceではない場合

  • 引数の型が参照の場合、参照を無視する 
  • 引数の型をパラメータの型とパターンマッチすることでTを決定する
template<typename T>
void f(T& param);

int x = 27;
const int cx = x;
const int& rx = x;

f(x); // Tはint、パラメータの型はint&
f(cx); // Tはconst int、パラメータの型はconst int&
f(rx); // Tはconst int、パラメータの型はconst int&
注目すべき点
  • 引数のconst性は保たれるので安全に使える
  • 参照を渡しても、(C++では禁止されている)参照の参照にはならない(自動的に余計な参照が除かれる)
パラメータがconst参照になった場合
template<typename T>
void f(const T& param);

f(x); // Tはint、パラメータの型はconst int&
f(cx); // Tはint、パラメータの型はconst int&
f(rx); // Tはint、パラメータの型はconst int&
自然なものとして納得できる
パラメータ型がポインタ型の場合も本質的に同様。
template<typename T>
void f(T* param);

int x = 27;
const int *px = &x;

f(&x); // Tはint、パラメータの型はint*
f(px); // Tはconst int、パラメータの型はconst int*

Case2: パラメータの型がUniversal Referenceの場合

Universal Referenceというのは初耳だったのでググったところ一番上に著者自身の説明が出てきました。
http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
Item26でも説明があるようです。
rvalue referenceと同じ見た目ですが、とりあえず型推定の時のみ登場するのでT&&とかauto&&がUniversal referenceだと当面の間は思っておけばいいっぽいです。
それで、その具体的な挙動については以下の通り、lvalueを引数として受けたっ時に挙動が直感的でないので要注意。

ルール
  • 引数がlvalueの時、Tとパラメータの型はlvalue referenceとなる
  • 引数がrvalueの時、通常の型推定のルールが適用される
template<typename T>
void f(T&& param);

int x = 27;
const int cx = x;
const int& rx = x;

f(x); // xはlvalueなので、Tはint&、パラメータの型もint&
f(cx); // cxはlvalueなので、Tはconst int&、パラメータの型もconst int&
f(rx); // rxはlvalueなので、Tはconst int&、パラメータの型もconst int&
f(27); // 27はrvalueなので、Tはint、パラメータの型はint&&

Case 3: パラメータ型がポインタでも参照でも無い場合

つまり、値渡しになる場合。

template<typename T>

void f(T param);

paramは引数のコピー、完全に新しいオブジェクトとなる。

ルール
  • 引数の型が参照の時、参照部分は無視される
  • 引数の型(参照の場合は参照を除去されたのち)constもvolatileも無視される
int x = 27;
const int cx = x;
const int& rx = x;

f(x); // Tもパラメータもint
f(cx); // Tもパラメータもint
f(rx); // Tもパラメータもint
パラメータは引数とは独立な新しいオブジェクトなので、const性は無視しても問題ないためこうなっている。
ただし、constが無視されるのは値渡しのパラメータ(今回のケース)の場合のみであることは重要。
template<typename T>
void f(T param);

const char* const ptr = "Fun with pointers";

f(ptr);

ptrはconstオブジェクトに対するconstポインタなので、アドレスも変更できないし値をnullにすることもできない。
これをfに渡した時、ポインタが値渡しとなるためTはconst char*となる。
つまり、ポインタに対するconstは失われ、ポインタの先のオブジェクトのconstは残る。

【読書ノート】Effective Modern C++ - Item 1 [2 / 2]

0 件のコメント:

コメントを投稿