コピーコンストラクタ
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 20/104。
コピーコンストラクターは、既存のオブジェクトのコピーとして新しいオブジェクトを作成します。同じ型の別のオブジェクトからオブジェクトを初期化するとき、オブジェクトを値渡しするとき、またはオブジェクトを値で返すときに呼び出されます。
コピーコンストラクターは、同じクラスのオブジェクトへの 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; }
};カスタムコピーコンストラクターがない場合、both オブジェクトは同じメモリを指すことになります。一方がdestroyedされると、もう一方にはダングリングポインターが残ります。深いコピーは新しいメモリを確保し、実際のdataをコピーすることで、各オブジェクトがそれぞれ自身のリソースを所有するようにします。
チャレンジ
簡単クラスが動的メモリを管理する場合に、ディープコピーの重要性を示すメッセージ履歴システムを構築しましょう。動的に確保された配列にメッセージを格納する MessageLog クラスを作成し、それぞれのコピーが独立したメモリを持つように、適切なコピーコンストラクターを実装します。
コードを整理するために、2つのファイルを作成します。
MessageLog.h: 動的メモリを使用してメッセージのコレクションを管理するMessageLogクラスを定義します。クラスには次の要素を含めます。- privateメンバー: メッセージを格納する文字列配列へのポインター、メッセージの最大数を表す
capacity、現在のメッセージ数を表すcount - capacityを受け取り、ヒープ上に配列を確保し、countを0に初期化するパラメーター付きコンストラクター
- ディープコピーを実行するコピーコンストラクター: 新しいメモリを確保し、ソースオブジェクトのすべてのメッセージをコピーします
- 確保されたメモリを解放し、
"MessageLog destroyed"と出力するデストラクター - 空きがある場合にメッセージを追加する
addMessage(string msg)メソッド - 現在のメッセージ数を返す
getCount()メソッド - 指定されたインデックス位置のメッセージを返す
getMessage(int index)メソッド
- privateメンバー: メッセージを格納する文字列配列へのポインター、メッセージの最大数を表す
main.cpp: コピーコンストラクターによって独立したコピーが作成されることを示します。入力からcapacityの値と2つのメッセージを読み込み、次の処理を行います。- 入力されたcapacityで
originalという名前のMessageLogを作成します - 両方のメッセージを
originalに追加します - コピーコンストラクターを使用して、
backupという名前のコピーを作成します - 3つ目のメッセージ
"New message"を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: "New message" を original のみに追加する
// TODO: Print "Original count: <count>"
// TODO: "Backup count: <count>" を出力する
// TODO: backup のすべてのメッセージを1行に1つずつ出力する
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタパラメータ付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ徹底解説Rule of Three / Five / Zero復習 - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () の演算子オーバーロード型変換演算子復習 - Matrixクラス自分で練習してみよう: C++オンラインコンパイラ