コピーするときは、オブジェクトの全体をコピーしよう - Effective C++ 第3版 12項-

#　はじめに

Effective C++ 第3版の12項 54ページから勉強していきます。
今回は、「コピーするときは、オブジェクトの全体をコピーしよう」についです。

# Effective C++ 第3版 - 12項　コピーするときは、オブジェクトの全体をコピーしよう -

## 前置き

今回は、「自作クラスのオブジェクトのコピー」について学んでいきます。

## 今回の勉強

### コピーコンストラクトとコピー代入演算子

オブジェクト指向システムでは、オブジェクトをコピー(「狭い意味でのコピー」と「代入」)できる関数は、コピーコンストラクタとコピー代入演算子です。
[5項](https://qiita.com/eierapfel/items/95635a86d3dee967a8d8)では、プログラム中で必要なときにコンパイラが、自動でコピーコンストラクタとコピー代入演算子を生成することを勉強しました。
自動で生成したこの2つの関数は、オブジェクトの全データの複製を行います。
自分でコピーコンストラクタとコピー代入演算子を書くのは、コンパイラが自動生成するものでは上手く行かない場合です。

今回は、Customer というクラスを作成して、クラス内に自分でコピーコンストラクタとコピー代入演算子を書いていきます。

```cpp:Customerクラス
class Customer {
 public:
  Customer(std::string name = "") : name_(name) {} // コンストラクタ
  ~Customer() {}
  Customer(const Customer& rhs);                   // コピーコンストラクタの宣言
  Customer& operator=(const Customer& rhs);        // コピー代入演算子の宣言

  std::string getName() { return name_; }

 private:
  std::string name_;
};

Customer::Customer(const Customer& rhs) : name_(rhs.name_) // rhsのデータをコピー
{
  std::cout << __PRETTY_FUNCTION__ << std::endl;
}

Customer& Customer::operator=(const Customer& rhs) {
  std::cout << __PRETTY_FUNCTION__ << std::endl;
  name_ = rhs.name_;          // rhsのデータをコピー
  return *this;
}
```

```cpp:実行例
Customer a("tanaka");
Customer b;
b = a;
std::cout << b.getName() << std::endl;
```

```:実行結果
Customer& Customer::operator=(const Customer&)
tanaka
```

上で書いたCustomerクラスは、実行例・実行結果からも分かるように上手く動作します。
ここで、Customerに新たにデータメンバを付け加えてみます。

```cpp
class Customer {
 public:
  Customer(std::string name = "", int date = 0) : name_(name), date_(date) {} // 追記
  ~Customer() {}
  Customer(const Customer& rhs);
  Customer& operator=(const Customer& rhs);

  std::string getName() { return name_; }
  int         getDate() { return date_; }   // 追記

 private:
  std::string name_;
  int         date_;                        // 付け加えたメンバ
};
```

```cpp:実行例
Customer a("tanaka", 2018);
Customer b;
b = a;
std::cout << b.getName() << std::endl;
std::cout << b.getDate() << std::endl;
```

```:実行結果
Customer& Customer::operator=(const Customer&)
tanaka
0
```

実行例・実行結果から分かるように、date_ のコピーができていないことが分かります。
しかし、コンパイラによる警告もエラーもなくビルドがされました。
そのため、クラスにメンバを追加し、コピーコンストラクタとコピー代入演算子を自作している場合は、必ず、コピーコンストラクトとコピー代入演算子にも追記をする必要があります。

```cpp:追記(コピーコンストラクタとコピー代入演算子)
Customer::Customer(const Customer& rhs) : name_(rhs.name_), date_(rhs.date_) { // 追記
  std::cout << __PRETTY_FUNCTION__ << std::endl;
}

Customer& Customer::operator=(const Customer& rhs) {
  std::cout << __PRETTY_FUNCTION__ << std::endl;
  name_ = rhs.name_;
  date_ = rhs.date_; // 追記
  return *this;
}
```

次にクラスの継承について考えていきます。
以下に、Customerを継承した PriorityCustomerクラスを書いていきます。

```cpp
class PriorityCustomer : public Customer {
 public:
  PriorityCustomer(std::string name = "", int date = 0, int priority = 0) : Customer(name, date), priority_(priority) {}
  ~PriorityCustomer() {}
  PriorityCustomer(const PriorityCustomer& rhs);
  PriorityCustomer& operator=(const PriorityCustomer& rhs);
  int               getPriority() { return priority_; }

 private:
  int priority_;
};

PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs) : priority_(rhs.priority_) {
  std::cout << __PRETTY_FUNCTION__ << std::endl;
}

PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs){
  std::cout << __PRETTY_FUNCTION__ << std::endl;
  priority_         = rhs.priority_;
  return *this;
}
```

PriorityCustomerのコピーコンストラクタとコピー代入演算子は、一見、PriorityCustomerのすべてをコピーしているように見えます。
しかし、Customerから継承しているデータメンバは、まったくコピーしていません。
そのため、派生クラス(PriorityCustomer)のコピーコンストラクタ・コピー代入演算子は、基底クラス(Customer)のコピーコンストラクタ・コピー代入演算子を呼び出す必要があります。
以下に、修正したPriorityCustomerのコピーコンストラクタとコピー代入演算子を記述します。

```cpp:修正したPriorityCustomerのコピーコンストラクタとコピー代入演算子
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
 : Customer(rhs),                               // 追記
   priority_(rhs.priority_) {
  std::cout << __PRETTY_FUNCTION__ << std::endl;
}

PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs){
  std::cout << __PRETTY_FUNCTION__ << std::endl;
  Customer::operator=(rhs);                     // 追記
  priority_         = rhs.priority_;
  return *this;
}
```

## サンプルコード

以下に、勉強で使用したコードを示します。

```cpp
#include <iostream>

class Customer {
 public:
  Customer(std::string name = "", int date = 0) : name_(name), date_(date) {}
  ~Customer() {}
  Customer(const Customer& rhs);
  Customer& operator=(const Customer& rhs);

  std::string getName() { return name_; }
  int         getDate() { return date_; }

 private:
  std::string name_;
  int         date_;
};

Customer::Customer(const Customer& rhs) : name_(rhs.name_), date_(rhs.date_) {
  std::cout << __PRETTY_FUNCTION__ << std::endl;
}

Customer& Customer::operator=(const Customer& rhs) {
  std::cout << __PRETTY_FUNCTION__ << std::endl;
  name_ = rhs.name_;
  date_ = rhs.date_;
  return *this;
}

class PriorityCustomer : public Customer {
 public:
  PriorityCustomer(std::string name = "", int date = 0, int priority = 0) : Customer(name, date), priority_(priority) {}
  ~PriorityCustomer() {}
  PriorityCustomer(const PriorityCustomer& rhs);
  PriorityCustomer& operator=(const PriorityCustomer& rhs);
  int               getPriority() { return priority_; }

 private:
  int priority_;
};

PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs) : Customer(rhs), priority_(rhs.priority_) {
  std::cout << __PRETTY_FUNCTION__ << std::endl;
}

PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs){
  std::cout << __PRETTY_FUNCTION__ << std::endl;
  Customer::operator=(rhs);
  priority_         = rhs.priority_;
  return *this;
}

int main() {
  std::cout << "12_qiita.cpp" << std::endl;
  Customer a("tanaka", 2018);
  Customer b;
  b = a;
  std::cout << b.getName() << std::endl;
  std::cout << b.getDate() << std::endl;
  
  PriorityCustomer c("tanaka", 2018, 5);
  PriorityCustomer d;
  d = c;
  std::cout << d.getName() << std::endl;
  std::cout << d.getDate() << std::endl;
  std::cout << d.getPriority() << std::endl;
  
}
```

## 実行結果

```:実行結果
12_qiita.cpp
Customer& Customer::operator=(const Customer&)
tanaka
2018
PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer&)
Customer& Customer::operator=(const Customer&)
tanaka
2018
5
```

## まとめ

今回は、自作クラスのオブジェクトのコピー時に注意するべきことについて学びました。

### 覚えておくこと

* コピー関数は、オブジェクトのデータメンバと基底クラス部分のすべてをコピーするように書かなければならない。
* 一方のコピー関数から他方のコピー関数を呼び出すようなコードを書いてはならない。
* かわりに、両者の共通部分を別の関数として定義し、それを呼び出すようにすれば良い。

## 参考文献

[1] https://www.amazon.co.jp/gp/product/4621066099/ref=dbs_a_def_rwt_hsch_vapi_taft_p1_i0