O Construtor Move em C++

Jinku Hu 12 outubro 2023
O Construtor Move em C++

Este artigo apresentará como usar o construtor de movimentação em C++.

Use o Construtor de movimento para fornecer controle de cópia eficiente para classes em C++

O controle de cópia da classe define as funções básicas necessárias para especificar o que acontece quando o objeto da classe é copiado, movido, atribuído ou destruído. Essas funções têm nomenclatura C++ especial, como as funções construtor de cópia e construtor de movimento definem como um objeto é inicializado com outro objeto do mesmo tipo. As funções de atribuição de cópia e atribuição de movimentação definem como o objeto é atribuído ao mesmo tipo de objeto. Destructor trata da rotina que é executada quando o objeto sai do escopo. É mais provável que algumas dessas funções sejam definidas pelo usuário, mas se não, o próprio compilador cria os protótipos padrão.

Quando o objeto de classe gerencia a memória dinâmica e os dados são muito grandes, as operações de cópia podem exigir muita computação. Eles podem usar recursos significativos que afetam o desempenho. Portanto, eles geralmente utilizam um construtor de movimento para reatribuir dados dinâmicos sem copiá-los para o novo local de memória. Isso é obtido atribuindo os ponteiros do objeto antigo aos membros correspondentes do objeto recém-inicializado ou atribuído. Observe que o exemplo a seguir não inclui o construtor de movimento e causa várias invocações do construtor de cópia, que delega para o construtor padrão.

#include <iostream>
#include <vector>

using std::cin;
using std::cout;
using std::endl;
using std::vector;

class MyClass {
 private:
  int* data;

 public:
  explicit MyClass(int d) {
    data = new int;
    *data = d;
    cout << "Constructor 1 is called" << endl;
  };

  MyClass(const MyClass& source) : MyClass(*source.data) {
    cout << "Copy Constructor is called " << endl;
  }

  int getData() const { return *data; }

  ~MyClass() {
    delete data;
    cout << "Destructor is called" << endl;
  }
};

void printVec(const vector<MyClass>& vec) {
  for (const auto& i : vec) {
    cout << i.getData() << " ";
  }
  cout << endl;
}

int main() {
  vector<MyClass> vec;

  vec.push_back(MyClass(10));
  vec.push_back(MyClass(11));
  printVec(vec);
  cout << "------------------" << endl;

  return EXIT_SUCCESS;
}

Resultado:

Constructor 1 is called
Constructor 1 is called
Copy Constructor is called
Destructor is called
Constructor 1 is called
Constructor 1 is called
Copy Constructor is called
Constructor 1 is called
Copy Constructor is called
Destructor is called
Destructor is called
10 11
------------------
Destructor is called
Destructor is called

Depois de definir um construtor de movimento, que geralmente deve ter a referência de valor r como o primeiro argumento (denotado com a notação &&), a inicialização do vetor se torna mais eficiente à medida que os novos elementos do tipo MyClass são adicionados. Como o construtor de movimento não aloca nova memória e assume o local mantido pelo objeto passado, é necessário atribuir nullptr aos membros do objeto anterior. Caso contrário, o destruidor tentará liberar o mesmo local da memória duas vezes, lançando o erro em tempo de execução.

#include <iostream>
#include <vector>

using std::cin;
using std::cout;
using std::endl;
using std::vector;

class MyClass {
 private:
  int* data;

 public:
  explicit MyClass(int d) {
    data = new int;
    *data = d;
    cout << "Constructor 1 is called" << endl;
  };

  MyClass(const MyClass& source) : MyClass(*source.data) {
    cout << "Copy Constructor is called " << endl;
  }

  MyClass(MyClass&& source) noexcept : data(source.data) {
    source.data = nullptr;
    cout << "Move Constructor is called" << endl;
  }

  int getData() const { return *data; }

  ~MyClass() {
    delete data;
    cout << "Destructor is called" << endl;
  }
};

void printVec(const vector<MyClass>& vec) {
  for (const auto& i : vec) {
    cout << i.getData() << " ";
  }
  cout << endl;
}

int main() {
  vector<MyClass> vec;

  vec.push_back(MyClass(10));
  vec.push_back(MyClass(11));
  printVec(vec);
  cout << "------------------" << endl;

  return EXIT_SUCCESS;
}

Resultado:

Constructor 1 is called
Move Constructor is called
Destructor is called
Constructor 1 is called
Move Constructor is called
Move Constructor is called
Destructor is called
Destructor is called
10 11
------------------
Destructor is called
Destructor is called
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