1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

 はじめに

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

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

前置き

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

今回の勉強

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

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

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

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;
}
実行例
Customer a("tanaka");
Customer b;
b = a;
std::cout << b.getName() << std::endl;
実行結果
Customer& Customer::operator=(const Customer&)
tanaka

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

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 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_ のコピーができていないことが分かります。
しかし、コンパイラによる警告もエラーもなくビルドがされました。
そのため、クラスにメンバを追加し、コピーコンストラクタとコピー代入演算子を自作している場合は、必ず、コピーコンストラクトとコピー代入演算子にも追記をする必要があります。

追記(コピーコンストラクタとコピー代入演算子)
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クラスを書いていきます。

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のコピーコンストラクタとコピー代入演算子を記述します。

修正した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;
}

サンプルコード

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

# 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

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?