C++ でのディープコピーとシャローコピー
この記事では、C++ でディープコピーとシャローコピーを使用する方法に関する複数の方法を示します。
浅いコピーは、C++ のデフォルトのコピーコンストラクターで使用される
C++ クラスは通常、コピー制御と総称されるいくつかの操作で定義され、ユーザーによって明示的に指定されるか、コンパイラーによって暗黙的に指定されます。これらのメンバー関数は、コピーコンストラクター、コピー割り当て演算子、ムーブコンストラクター、ムーブ代入演算子、およびデストラクタとして表されます。コピーコンストラクターと移動コンストラクターは、オブジェクトが同じタイプの別のオブジェクトから初期化されるときに発生する操作を実装します。ただし、これらの関数がコンパイラによって暗黙的に合成されると、一部のクラスタイプが正しく動作しない場合があります。たとえば、動的メモリを管理するクラスは、手動で割り当てる必要のあるデータメンバーを共有します。したがって、プログラマーは上記のメンバー関数を明示的に実装する責任があります。
この場合、2つの std::string データメンバーを持つ Person という名前のクラスのコピーコンストラクターのケースを示します。そのうちの 1つは new 演算子を使用して割り当てられます。次のサンプルコードは、コピーコンストラクターが明示的に定義されていない場合に何が起こるかを示しており、Person オブジェクトを別の Person オブジェクトで初期化します。P1 には文字列が格納されていることに注意してください-初期化後に Buddy/Rich、P2 はステートメントでコピーコンストラクターが呼び出された後も同じ値になります-Person P2 = P1;。renamePerson 関数が P1 オブジェクトで実行された後、P2 オブジェクトの surname データメンバーも変更されます。
#include <iostream>
#include <string>
#include <utility>
#include <vector>
using std::cout;
using std::endl;
using std::string;
using std::vector;
class Person {
public:
Person() = default;
Person(string n, string s) {
name = std::move(n);
surname = new string(std::move(s));
}
~Person() { delete surname; }
void renamePerson(const string &n, const string &s) {
name.assign(n);
surname->assign(s);
};
string &getName() { return name; };
string &getSurname() { return *surname; };
void printPerson() { cout << name << " " << *surname; }
private:
string name;
string *surname{};
};
int main() {
Person P1("Buddy", "Rich");
Person P2 = P1;
P1.printPerson();
cout << endl;
P2.printPerson();
cout << endl;
P1.renamePerson("Heinz", "Lulu");
P1.printPerson();
cout << endl;
P2.printPerson();
cout << endl;
exit(EXIT_SUCCESS);
}
出力:
Buddy Rich
Buddy Rich
Heinz Lulu
Buddy Lulu
C++ でカスタムコピーコンストラクターを使用してディープコピー動作を実装する
一方、Person クラスのカスタムコピーコンストラクターを実装すると、正しく動作し、P1.renamePerson("Heinz", "Lulu") ステートメントの後の P2 オブジェクトは変更されません。前のコードスニペットでは、P2 オブジェクトの surname メンバーが P1 オブジェクトと同じ文字列を指し、renamePerson が両方のオブジェクトを変更しました。今回、P2 は動的メモリに割り当てられた独自の surname メンバーを持ち、P1 オブジェクトと共有しません。
#include <iostream>
#include <string>
#include <utility>
#include <vector>
using std::cout;
using std::endl;
using std::string;
using std::vector;
class Person {
public:
Person() = default;
Person(string n, string s) {
name = std::move(n);
surname = new string(std::move(s));
}
Person(Person &p) {
name = p.name;
surname = new string(*p.surname);
}
~Person() { delete surname; }
void renamePerson(const string &n, const string &s) {
name.assign(n);
surname->assign(s);
};
string &getName() { return name; };
string &getSurname() { return *surname; };
void printPerson() { cout << name << " " << *surname; }
private:
string name;
string *surname{};
};
int main() {
Person P1("Buddy", "Rich");
Person P2 = P1;
P1.printPerson();
cout << endl;
P2.printPerson();
cout << endl;
P1.renamePerson("Heinz", "Lulu");
P1.printPerson();
cout << endl;
P2.printPerson();
cout << endl;
exit(EXIT_SUCCESS);
}
出力:
Buddy Rich
Buddy Rich
Heinz Lulu
Buddy Rich
