C++ でのディープコピーとシャローコピー

胡金庫 2023年10月12日
  1. 浅いコピーは、C++ のデフォルトのコピーコンストラクターで使用される
  2. C++ でカスタムコピーコンストラクターを使用してディープコピー動作を実装する
C++ でのディープコピーとシャローコピー

この記事では、C++ でディープコピーとシャローコピーを使用する方法に関する複数の方法を示します。

浅いコピーは、C++ のデフォルトのコピーコンストラクターで使用される

C++ クラスは通常、コピー制御と総称されるいくつかの操作で定義され、ユーザーによって明示的に指定されるか、コンパイラーによって暗黙的に指定されます。これらのメンバー関数は、コピーコンストラクターコピー割り当て演算子ムーブコンストラクタームーブ代入演算子、およびデストラクタとして表されます。コピーコンストラクターと移動コンストラクターは、オブジェクトが同じタイプの別のオブジェクトから初期化されるときに発生する操作を実装します。ただし、これらの関数がコンパイラによって暗黙的に合成されると、一部のクラスタイプが正しく動作しない場合があります。たとえば、動的メモリを管理するクラスは、手動で割り当てる必要のあるデータメンバーを共有します。したがって、プログラマーは上記のメンバー関数を明示的に実装する責任があります。

この場合、2つの std::string データメンバーを持つ Person という名前のクラスのコピーコンストラクターのケースを示します。そのうちの 1つは new 演算子を使用して割り当てられます。次のサンプルコードは、コピーコンストラクターが明示的に定義されていない場合に何が起こるかを示しており、Person オブジェクトを別の Person オブジェクトで初期化します。P1 には文字列が格納されていることに注意してください-初期化後に Buddy/RichP2 はステートメントでコピーコンストラクターが呼び出された後も同じ値になります-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
著者: 胡金庫
胡金庫 avatar 胡金庫 avatar

DelftStack.comの創設者です。Jinku はロボティクスと自動車産業で8年以上働いています。自動テスト、リモートサーバーからのデータ収集、耐久テストからのレポート作成が必要となったとき、彼はコーディングスキルを磨きました。彼は電気/電子工学のバックグラウンドを持っていますが、組み込みエレクトロニクス、組み込みプログラミング、フロントエンド/バックエンドプログラミングへの関心を広げています。

LinkedIn Facebook

関連記事 - C++ Class