はじめに
Effective C++ 第3版の12項 54ページから勉強していきます。
今回は、「コピーするときは、オブジェクトの全体をコピーしよう」についです。
Effective C++ 第3版 - 12項 コピーするときは、オブジェクトの全体をコピーしよう -
前置き
今回は、「自作クラスのオブジェクトのコピー」について学んでいきます。
今回の勉強
コピーコンストラクトとコピー代入演算子
オブジェクト指向システムでは、オブジェクトをコピー(「狭い意味でのコピー」と「代入」)できる関数は、コピーコンストラクタとコピー代入演算子です。
5項では、プログラム中で必要なときにコンパイラが、自動でコピーコンストラクタとコピー代入演算子を生成することを勉強しました。
自動で生成したこの2つの関数は、オブジェクトの全データの複製を行います。
自分でコピーコンストラクタとコピー代入演算子を書くのは、コンパイラが自動生成するものでは上手く行かない場合です。
今回は、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(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