Der Move-Konstruktor in C++

Jinku Hu 12 Oktober 2023
Der Move-Konstruktor in C++

In diesem Artikel wird die Verwendung des Move-Konstruktors in C++ vorgestellt.

Verwenden Sie den Move-Konstruktor, um eine effiziente Kopiersteuerung für Klassen in C++ bereitzustellen

Die Kopiersteuerung der Klasse definiert die Kernfunktionen, die erforderlich sind, um festzulegen, was passiert, wenn das Klassenobjekt kopiert, verschoben, zugewiesen oder zerstört wird. Diese Funktionen haben eine spezielle C++-Nomenklatur, wie die Kopierkonstruktor- und Bewegungskonstruktor-Funktionen definieren, wie ein Objekt mit einem anderen Objekt desselben Typs initialisiert wird. Kopierzuweisungs- und Verschiebezuweisungsfunktionen definieren, wie das Objekt demselben Objekttyp zugewiesen wird. Destructor behandelt die Routine, die ausgeführt wird, wenn das Objekt den Gültigkeitsbereich verlässt. Einige dieser Funktionen werden höchstwahrscheinlich vom Benutzer definiert, aber wenn nicht, erstellt der Compiler selbst die Standardprototypen.

Wenn das Klassenobjekt den dynamischen Speicher verwaltet und die Daten ziemlich groß sind, können die Kopiervorgänge ziemlich rechenintensiv sein. Sie können erhebliche Ressourcen verbrauchen, die die Leistung beeinträchtigen. Daher verwenden sie häufig einen Bewegungskonstruktor, um dynamische Daten neu zuzuweisen, ohne sie an die neue Speicherstelle zu kopieren. Dies wird erreicht, indem die Zeiger des alten Objekts den entsprechenden Mitgliedern des neu initialisierten oder zugewiesenen Objekts zugewiesen werden. Beachten Sie, dass das folgende Beispiel den move-Konstruktor nicht enthält und mehrere Aufrufe des Kopierkonstruktors verursacht, der an den Standardkonstruktor delegiert.

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

Ausgabe:

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

Sobald wir einen Bewegungskonstruktor definieren, der im Allgemeinen eine r-Wert-Referenz als erstes Argument annehmen sollte (mit &&-Notation gekennzeichnet), wird die Vektorinitialisierung effizienter, wenn die neuen Elemente vom Typ MyClass hinzugefügt werden. Da der Move-Konstruktor keinen neuen Speicher zuweist und die Position des übergebenen Objekts übernimmt, muss man den Membern des vorherigen Objekts nullptr zuweisen. Andernfalls versucht der Destruktor, denselben Speicherort zweimal freizugeben, und löst den Laufzeitfehler aus.

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

Ausgabe:

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

Verwandter Artikel - C++ Class