#10 代入演算子は *this の参照を戻す

ということで,Effective C++の10項の内容です.

コンストラクタ,コピーコンストラクタの暗黙呼び出し

ということで,ちょっと内容とずれてしまうのですが寄り道です.そもそも,コンストラクタとかコピーコンストラクタってコンパイラが暗黙の呼び出しをしてくれます.この辺をちゃんと理解すべく,簡単にまとめておきます.サンプルケースとして,下記のクラスを作りました.

#include <iostream>

class IntNumber {
public:
  IntNumber(int val) 
    : num(val) {
    std::cout << "IntNumber(int val)" << std::endl;
  }

  ~IntNumber() {
    std::cout << "~IntNumber" << std::endl;
  }

  IntNumber(const IntNumber& obj) 
    : num(obj.num) {
    std::cout << "IntNumber(const IntNumber& obj)" << std::endl;
  }

  IntNumber& operator=(const IntNumber& val) {
    std::cout << "IntNumber& operator=(const IntNumber& val)" << std::endl;
    if (this != &val) {
      this->num = val.num;
    }
    return *this;
  }

private:
  int num;
};

int main(int, char**) {

  // 1.直接的なコンストラクタのコール
  std::cout << "1. Explicit Constructor Call" << std::endl;
  IntNumber num1(1);
  std::cout << std::endl;

  // 2.暗黙的なコンストラクタのコール
  std::cout << "2. Implicit Constructor Call" << std::endl;
  IntNumber num2 = 2;
  std::cout << std::endl;

  // 3.明示的なコピーコンストラクタのコール
  std::cout << "3. Explicit Copy Constructor Call" << std::endl;
  IntNumber num3(num1);
  std::cout << std::endl;

  // 4.暗黙的なコピーコンストラクタのコール
  std::cout << "4. Implicit Copy Constructor Call" << std::endl;
  IntNumber num4 = num1;
  std::cout << std::endl;

  // 5.明示的なコピー代入演算子のコール
  std::cout << "5. Explicit operator= Call" << std::endl;
  num1.operator=(num2.operator=(num3.operator=(num4)));
  std::cout << std::endl;

  // 6.コピー代入演算子のコール
  std::cout << "6. Implicit operator= Call" << std::endl;
  num1 = num2 = num3 = num4;
  std::cout << std::endl;

  return 0;
}

コンストラクタコール

上記サンプルコードですが,main関数の下記の部分に関しては定義と全く同じ形でコンストラクタを読んでいるので,想像どおりコンストラクタが呼ばれます.

  // 1.直接的なコンストラクタのコール
  std::cout << "1. Explicit Constructor Call" << std::endl;
  IntNumber num1(1);
  std::cout << std::endl;

では,次のケースはどうでしょうか?

  // 2.暗黙的なコンストラクタのコール
  std::cout << "2. Implicit Constructor Call" << std::endl;
  IntNumber num2 = 2;
  std::cout << std::endl;

このケースでは,”コピー代入演算子が呼ばれる??”ってことはなく,コンストラクタが呼ばれます.おそらく,コンパイラのなかで下記のような順番でフィットする関数を探しているのだと思います.
1.num2は今までに作成されていないオブジェクト.なので,オブジェクトを新規作成する必要あり.
2.= の右辺が int だけど,int から直接的にオブジェクトを作れるのは,,,,"IntNumber(int val)".ということで,コンストラクタをコール.

コピーコンストラクタコール

続いて同じくコピーコンストラクタのケースを考えます.下記のケースでは,定義と同じ形でコピーコンストラクタを読んでいるので,コピーコンストラクタが呼ばれます.

  // 3.明示的なコピーコンストラクタのコール
  std::cout << "3. Explicit Copy Constructor Call" << std::endl;
  IntNumber num3(num1);
  std::cout << std::endl;

では,次のケースでは?こちらも暗黙的なコンストラクタコールのときと同様に,,,
1.num4は今までに作成されていないオブジェクト.なので,オブジェクトを新規作成する必要あり.
2.= の右辺が IntNumber だけど,IntNumber から直接的にオブジェクトを作れるのは,,,,"IntNumber(const IntNumber& obj)",ということで,コピーコンストラクタをコール.

  // 4.暗黙的なコピーコンストラクタのコール
  std::cout << "4. Implicit Copy Constructor Call" << std::endl;
  IntNumber num4 = num1;
  std::cout << std::endl;

コピー代入演算子コール

続いてコピー代入演算子ですが, "operatorxx" って普通の関数と見た目が違っててちょっとわかりにくいです.ただ "operatorxx" って明示的に関数を呼ぶと,わりとすっきりわかりやすくなります.サンプルコードの例では,,,,

  // 5.明示的なコピー代入演算子のコール
  std::cout << "5. Explicit operator= Call" << std::endl;
  num1.operator=(num2.operator=(num3.operator=(num4)));
  std::cout << std::endl;

  // 6.コピー代入演算子のコール
  std::cout << "6. Implicit operator= Call" << std::endl;
  num1 = num2 = num3 = num4;
  std::cout << std::endl;

上記の5と6は等価です.やっとEffective C++10項の内容になりますが,”代入演算子は基本的に (a = b = c) っていう書き方ができないといけない!”ってことを言ってます.

"a = b = c"

って書くと,組み込み型では c を b に代入して,(その結果が反映された) b を a に代入する.っていう意味になると思いますがこれって結局明示的に代入演算子を呼ぶと

a.operator=(b.operator=(c))

ってことなんですよね.だから,operator= が参照を返す必要があるということになります.