Copia profunda VS copia superficial en C++

Jinku Hu 12 octubre 2023
  1. El constructor de copia predeterminado utiliza la copia superficial en C++
  2. Utilice el constructor de copia personalizado para implementar el comportamiento de copia profunda en C++
Copia profunda VS copia superficial en C++

Este artículo demostrará varios métodos sobre cómo usar la copia profunda VS la copia superficial en C++.

El constructor de copia predeterminado utiliza la copia superficial en C++

Las clases de C++ se definen generalmente con varias operaciones, denominadas colectivamente control de copia, especificadas explícitamente por el usuario o implícitamente por el compilador. Estas funciones miembro se denominan: constructor de copia, operador de asignación de copia, constructor de movimiento, operador de asignación de movimiento y destructor. Copiar constructor y mover constructor implementan operaciones que suceden cuando el objeto se inicializa desde otro objeto del mismo tipo. Aunque, cuando el compilador sintetiza implícitamente estas funciones, algunos tipos de clases pueden comportarse incorrectamente. Por ejemplo, las clases que administran la memoria dinámica compartirán miembros de datos que deben asignarse manualmente. Por lo tanto, el programador es responsable de implementar explícitamente las funciones miembro anteriores.

En este caso, demostramos el caso de un constructor de copia en una clase llamada Person con dos miembros de datos std::string, uno de los cuales se asigna mediante el operador new. El siguiente código de ejemplo muestra lo que sucede cuando el constructor de copia no está definido explícitamente e inicializamos un objeto Person con otro objeto Person. Observe que P1 ha almacenado cadenas - Buddy/Rich después de la inicialización y P2 tiene los mismos valores después de que se llama al constructor de copia en la instrucción - Person P2 = P1;. Después de ejecutar la función renamePerson en el objeto P1, también se modifica el miembro de datos surname del objeto P2.

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

Producción :

Buddy Rich
Buddy Rich
Heinz Lulu
Buddy Lulu

Utilice el constructor de copia personalizado para implementar el comportamiento de copia profunda en C++

Por otro lado, cuando implementamos un constructor de copia personalizado para la clase Person, se comporta correctamente y no modifica el objeto P2 después de la declaración P1.renamePerson("Heinz", "Lulu"). En el fragmento de código anterior, el miembro apellido del objeto P2 apuntaba a la misma cadena que el objeto P1, y renamePerson modificó ambos objetos. Esta vez, P2 tiene su propio miembro apellido asignado en la memoria dinámica y no lo comparte con el objeto 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);
}

Producción :

Buddy Rich
Buddy Rich
Heinz Lulu
Buddy Rich
Autor: 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

Artículo relacionado - C++ Class