Copie profonde VS Copie superficielle en C++

Jinku Hu 12 octobre 2023
  1. La copie superficielle est utilisée par le constructeur de copie par défaut en C++
  2. Utiliser le constructeur de copie personnalisée pour implémenter le comportement de copie profonde dans C++
Copie profonde VS Copie superficielle en C++

Cet article présente plusieurs méthodes sur la façon d’utiliser la copie profonde VS la copie superficielle en C++.

La copie superficielle est utilisée par le constructeur de copie par défaut en C++

Les classes C++ sont généralement définies avec plusieurs opérations, collectivement appelées contrôle de copie, spécifiées explicitement par l’utilisateur ou implicitement par le compilateur. Ces fonctions membres sont désignées par: constructeur de copie, opérateur d'affectation de copie, constructeur de déplacement, opérateur d'affectation de déplacement et destructeur. Copier le constructeur et déplacer le constructeur implémentent des opérations qui se produisent lorsque l’objet est initialisé à partir d’un autre objet du même type. Bien que, lorsque ces fonctions sont synthétisées implicitement par le compilateur, certains types de classe peuvent se comporter de manière incorrecte. Par exemple, les classes qui gèrent la mémoire dynamique partageront des membres de données qui doivent être alloués manuellement. Ainsi, le programmeur est responsable de l’implémentation explicite des fonctions membres ci-dessus.

Dans ce cas, nous démontrons le cas d’un constructeur de copie dans une classe nommée Person avec deux membres de données std::string, dont l’un est alloué à l’aide de l’opérateur new. L’exemple de code suivant montre ce qui se passe lorsque le constructeur de copie n’est pas défini explicitement et que nous initialisons un objet Person avec un autre objet Person. Notez que P1 a stocké des chaînes - Buddy/Rich après l’initialisation et P2 a les mêmes valeurs après l’appel du constructeur de copie dans l’instruction - Person P2 = P1;. Après l’exécution de la fonction renamePerson sur l’objet P1, la donnée membre surname de l’objet P2 est également modifiée.

#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);
}

Production:

Buddy Rich
Buddy Rich
Heinz Lulu
Buddy Lulu

Utiliser le constructeur de copie personnalisée pour implémenter le comportement de copie profonde dans C++

Par contre, lorsque nous implémentons un constructeur de copie personnalisé pour la classe Person, il se comporte correctement et ne modifie pas l’objet P2 après l’instruction P1.renamePerson("Heinz", "Lulu"). Dans l’extrait de code précédent, le membre surname de l’objet P2 pointait sur la même chaîne que l’objet P1, et renamePerson modifiait les deux objets. Cette fois, P2 a son propre membre surname alloué sur la mémoire dynamique et ne le partage pas avec l’objet 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);
}

Production:

Buddy Rich
Buddy Rich
Heinz Lulu
Buddy Rich
Auteur: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.

LinkedIn Facebook

Article connexe - C++ Class