Deep Copy VS Shallow Copy em C++

Jinku Hu 12 outubro 2023
  1. A cópia superficial é usada pelo construtor de cópia padrão em C++
  2. Use o construtor de cópia personalizada para implementar o comportamento de cópia profunda em C++
Deep Copy VS Shallow Copy em C++

Este artigo demonstrará vários métodos sobre como usar a cópia superficial do VS em cópia profunda em C++.

A cópia superficial é usada pelo construtor de cópia padrão em C++

As classes C++ são geralmente definidas com várias operações, coletivamente referidas como controle de cópia, especificadas explicitamente pelo usuário ou implicitamente pelo compilador. Essas funções-membro são denotadas como: construtor de cópia, operador de atribuição de cópia, construtor de movimentação, operador de atribuição de movimentação e destruidor. O construtor de cópia e o construtor de movimento implementam operações que acontecem quando o objeto é inicializado a partir de outro objeto do mesmo tipo. Embora, quando essas funções são sintetizadas pelo compilador implicitamente, alguns tipos de classe podem se comportar incorretamente. Por exemplo, as classes que gerenciam a memória dinâmica compartilharão membros de dados que precisam ser alocados manualmente. Portanto, o programador é responsável por implementar as funções-membro acima explicitamente.

Nesse caso, demonstramos o caso de um construtor de cópia em uma classe chamada Person com dois membros de dados std::string, um dos quais é alocado usando o operador new. O código de exemplo a seguir mostra o que acontece quando o construtor de cópia não é definido explicitamente e inicializamos um objeto Person com outro objeto Person. Observe que P1 armazenou strings - Buddy/Rich após a inicialização e P2 tem os mesmos valores depois que o construtor de cópia é chamado na instrução - Person P2 = P1;. Após a função renamePerson ser executada no objeto P1, o membro de dados surname do objeto P2 também é modificado.

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

Resultado:

Buddy Rich
Buddy Rich
Heinz Lulu
Buddy Lulu

Use o construtor de cópia personalizada para implementar o comportamento de cópia profunda em C++

Por outro lado, quando implementamos um construtor de cópia personalizado para a classe Person, ele se comporta corretamente e não modifica o objeto P2 após a instrução P1.renamePerson("Heinz", "Lulu"). No fragmento de código anterior, o membro surname do objeto P2 apontava para a mesma string que o objeto P1 e renamePerson modificou ambos os objetos. Desta vez, P2 tem seu próprio membro surname alocado na memória dinâmica e não o compartilha com o 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);
}

Resultado:

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

Artigo relacionado - C++ Class