コピーコンストラクタ
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 20/104。
コピーコンストラクターは、既存のオブジェクトのコピーとして新しいオブジェクトを作成します。同じ型の別のオブジェクトからオブジェクトを初期化するとき、オブジェクトを値渡しするとき、またはオブジェクトを値として返すときに呼び出されます。
コピーコンストラクターは、同じクラスのオブジェクトへの const 参照を受け取ります。参照でなければなりません。other を値渡しすると、それ自体のコピーが必要になり、コピーコンストラクターが何度も呼び出されて無限に続いてしまいます。コピーを作成しても元のオブジェクトは決して変更されないように、const が指定されています。
class Player {
std::string name;
int health;
public:
Player(std::string n, int h) : name(n), health(h) {}
// コピーコンストラクタ
Player(const Player& other) {
name = other.name;
health = other.health;
}
};
Player original("Hero", 100);
Player copy = original; // コピーコンストラクタが呼び出される
Player another(original); // こちらもコピーコンストラクタを呼び出すコピーコンストラクターを定義しない場合、コンパイラーは浅いコピーを行うものを生成します。つまり、各メンバーの値をそのままコピーします。これは単純な型では問題なく機能しますが、クラスが動的メモリを管理している場合は問題が発生します。
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s) {
data = new int[size];
}
// ディープコピーコンストラクタ
Buffer(const Buffer& other) : size(other.size) {
data = new int[size]; // 新しいメモリを確保する
for (size_t i = 0; i < size; i++)
data[i] = other.data[i]; // 内容をコピーする
}
~Buffer() { delete[] data; }
};独自のコピーコンストラクターがなければ、両方のオブジェクトが同じメモリを指すことになります。一方が破棄されると、もう一方はダングリングポインターを持つことになります。ディープコピーでは、新しいメモリを割り当てて実際のデータをコピーするため、各オブジェクトがそれぞれ自身のリソースを所有できます。
チャレンジ
簡単クラスが動的メモリを管理する場合に、深いコピーが重要であることを示すメッセージ履歴システムを作りましょう。メッセージを動的に確保した配列に格納するMessageLogクラスを作成し、各コピーが独立したメモリを持つように適切なコピーコンストラクターを実装します。
コードを整理するために、2つのファイルを作成します。
MessageLog.h: 動的メモリを使ってメッセージのコレクションを管理するMessageLogクラスを定義します。クラスには次のものを含めます。- プライベートメンバー: メッセージを格納する文字列配列へのポインター、メッセージの最大数を表す
capacity、現在のメッセージ数を表すcount - capacityを受け取り、ヒープ上に配列を確保し、countを0に初期化する引数付きコンストラクター
- 深いコピーを行うコピーコンストラクター: 新しいメモリを確保し、コピー元オブジェクトからすべてのメッセージをコピーする
- 確保したメモリを解放し、
"MessageLog destroyed"を出力するデストラクター - 空きがあればメッセージを追加する
addMessage(string msg)メソッド - 現在のメッセージ数を返す
getCount()メソッド - 指定されたインデックスのメッセージを返す
getMessage(int index)メソッド
- プライベートメンバー: メッセージを格納する文字列配列へのポインター、メッセージの最大数を表す
main.cpp: コピーコンストラクターが独立したコピーを作成することを示します。入力からcapacityの値と2つのメッセージを読み取り、次の処理を行います。- 入力されたcapacityで
originalというMessageLogを作成する - 両方のメッセージを
originalに追加する - コピーコンストラクターを使って
backupというコピーを作成する "New message"という3つ目のメッセージをoriginalだけに追加する"Original count: <count>"を出力する"Backup count: <count>"を出力するbackupのすべてのメッセージを1行に1つずつ出力する
- 入力されたcapacityで
入力形式は次のとおりです。
- 1行目: capacity(整数)
- 2行目: 1つ目のメッセージ(文字列)
- 3行目: 2つ目のメッセージ(文字列)
深いコピーが正しく実装されていれば、backupのメッセージ数は2、originalのメッセージ数は3になり、それぞれが独立したメモリを持つことが確認できます。適切なコピーコンストラクターがなければ、どちらも同じ配列を共有し、一方への変更がもう一方にも影響してしまいます。
#include "MessageLog.h"を使って、ヘッダーファイルをmain.cppにインクルードしてください。
自分で試してみよう
#include <iostream>
#include <string>
#include "MessageLog.h"
using namespace std;
int main() {
// 入力を読み取る
int capacity;
cin >> capacity;
cin.ignore();
string msg1, msg2;
getline(cin, msg1);
getline(cin, msg2);
// TODO: 入力された容量を持つ、'original' という名前の MessageLog を作成する
// TODO: 両方のメッセージを original に追加する
// TODO: コピーコンストラクタを使用して、'backup' という名前のコピーを作成する
// TODO: original にのみ "New message" を追加する
// TODO: "Original count: <count>" を出力する
// TODO: "Backup count: <count>" を出力する
// TODO: backup のすべてのメッセージを1行に1つずつ出力する
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタパラメータ付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ徹底解説Rule of Three / Five / Zero復習 - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () の演算子オーバーロード型変換演算子復習 - Matrixクラス自分で練習してみよう: C++オンラインコンパイラ