Intelligente Zeiger in C++ verstehen

Mohd Mohtashim Nawaz 12 Oktober 2023
  1. Implementierung eines intelligenten Zeigers in C++
  2. Verwendung des Smart-Pointer-Destruktors zur Verhinderung von Speicherlecks in C++
  3. Typen von Smart Pointern in C++
  4. Wann sollten Smart-Pointer in C++ verwendet werden?
Intelligente Zeiger in C++ verstehen

Dieser Artikel behandelt die intelligenten Zeiger in C++, wie sie Speicherlecks verhindern, die Arten von intelligenten Zeigern und die Fälle, in denen wir sie verwenden sollten.

Implementierung eines intelligenten Zeigers in C++

Wir können den intelligenten Zeiger in C++ als Klassenvorlage definieren, die wir verwenden können, um den ursprünglichen Rohzeiger beizubehalten. Unsere Smart-Pointer-Klasse enthält eine Pointer-Variable, die unsere Rohzeiger-, Destruktor- und Operator-Überschreibungsmethoden enthält.

Wir sind jedoch nicht darauf beschränkt, nur diese Felder und Verfahren einzubeziehen. Wir können unsere Methoden nach Bedarf hinzufügen.

Anstatt den rohen Zeiger direkt zu handhaben, verwenden wir diese Klasse, um den Zeiger zu handhaben. Und anstatt eine benutzerdefinierte Klasse für intelligente Zeiger in C++ zu definieren, können wir auch die Standard-C++-Bibliothek verwenden.

Lassen Sie uns eine benutzerdefinierte Klasse für die Implementierung intelligenter C++-Zeiger definieren.

#include <iostream>

using namespace std;

template <class T>
class CustomSmartPointer {
  T *data;

 public:
  explicit CustomSmartPointer(T *ptr = NULL)  // Constructor to assign pointer
  {
    data = ptr;
  }
  ~CustomSmartPointer()  // Destructor that deallocated memory
  {
    delete data;
  }
  // We overload * and -> operators
  T *operator->() { return data; }
  T &operator*() { return *data; }
};

int main() {
  CustomSmartPointer<int> myPtr(new int());
  *myPtr = 100;
  cout << *myPtr << endl;
  // After executing above statement, memory allocated to myPtr is deallocated.
  return 0;
}

Ausgabe:

stark@stark:~/eclipse-workspace/smart_pointer$ g++ custom_smart_prt.cc
stark@stark:~/eclipse-workspace/smart_pointer$ ./a.out
100

Verwendung des Smart-Pointer-Destruktors zur Verhinderung von Speicherlecks in C++

Das Vorhandensein eines Destruktors macht unsere Smart-Pointer-Klasse zu etwas Besonderem und unterscheidet sie von Raw-Pointern. Wir verwenden den Destruktor für intelligente Zeiger von C++, um den unserem Zeiger zugewiesenen Speicher freizugeben.

Ein Destruktor wird automatisch aufgerufen und gibt den zugewiesenen Speicher frei, wenn ein Klassenobjekt den Gültigkeitsbereich verlässt. Wenn wir den Speicherfreigabeprozess automatisiert haben, brauchen wir uns keine Gedanken über Ressourcenlecks zu machen, die durch die Zeiger verursacht werden, die wir möglicherweise vergessen haben, aufzuheben.

Der Smart-Pointer-Destruktor verhält sich wie ein automatischer Garbage Collector in C++, ähnlich wie Java und C#.

Sehen wir uns ein Beispiel für ein Ressourcenleck an, das durch Rohzeiger verursacht wird.

#include <iostream>

using namespace std;

class student {
 private:
  string name;
  int marks;

 public:
  void setMarks(int m) { marks = m; }
  void setName(string n) { name = n; }
  int getMarks() { return marks; }
  string getName() { return name; }
};

int main() {
  while (true) {
    student* p = new student;
    *p->name = "Stark";
    *p->marks = 100;
  }
  return 0;
}

Wie wir sehen können, initialisieren wir den Zeiger des Schülertyps in einer Endlosschleife. Wir haben den Zeigerspeicher nicht freigegeben, daher würden die Ressourcen so lange zugewiesen, bis der gesamte Speicher belegt ist.

Wir sollten dieses Programm nicht lange ausführen. Andernfalls könnte unser System aufgrund eines Speicherüberlaufs hängen bleiben.

Wenn wir jedoch den Smart Pointer verwendet hätten, wäre der Speicher nach jeder Schleife automatisch freigegeben worden. Somit wäre jeweils nur ein Zeigerspeicher belegt gewesen.

Typen von Smart Pointern in C++

In C++ gibt es drei verschiedene Arten von intelligenten Zeigern. Dies sind die Typen, die in der C++-Bibliothek für intelligente Zeiger implementiert sind.

unique_ptr

Mit diesem intelligenten Zeigertyp können wir nur einen einzigen Benutzer für den zugrunde liegenden Rohzeiger zuweisen. Das bedeutet, dass wir zwei oder mehr Objekten nicht denselben zugrunde liegenden Zeiger zuweisen können.

Wir sollten diesen Zeigertyp standardmäßig verwenden, bis wir den Speicher teilen können.

shared_ptr

Wie der Name schon sagt, können wir mit diesem Smart Pointer einem Rohzeiger mehrere Eigentümer zuweisen. Dies verwendet einen Referenzzähler, um die Anzahl der Zugewiesenen zu verfolgen.

weak_ptr

Dieser Smart-Pointer-Typ ist dem shared_ptr sehr ähnlich, nur dass er nicht am Referenzzähler teilnimmt. Das bedeutet nicht, dass es keinen Referenzzähler hat, aber seine Referenz wird nicht gezählt.

Stattdessen verwenden wir seinen Referenzzähler, um andere gemeinsam genutzte Referenzen zu zählen. Wir können diesen Zeiger verwenden, wenn wir den Besitz nicht streng kontrollieren müssen.

Es bietet Zugriff auf ein oder mehrere shared_ptr-Objekte. Es hat jedoch einen Vorteil, da es die Möglichkeit eines Deadlocks beseitigt.

Sehen wir uns eine Implementierung eines intelligenten C++-Zeigers an.

#include <iostream>
#include <memory>

using namespace std;

class student {
 private:
  string name;
  int marks;

 public:
  void setMarks(int m) { marks = m; }
  void setName(string n) { name = n; }
  int getMarks() { return marks; }
  string getName() { return name; }
};

int main() {
  unique_ptr<student> ptr1(new student);
  ptr1->setName("Stark");
  ptr1->setMarks(100);
  cout << "unique_ptr output >>\n";
  cout << ptr1->getName() << " : " << ptr1->getMarks() << "\n";

  cout << "shared_ptr output >> \n";
  shared_ptr<student> ptr2(new student);
  ptr2->setName("Tony");
  ptr2->setMarks(99);
  cout << ptr2->getName() << " : " << ptr2->getMarks() << "\n";

  shared_ptr<student> ptr22;
  ptr22 = ptr2;
  cout << ptr22->getName() << " : " << ptr22->getMarks() << "\n";
  cout << "Reference count of shared_ptr: " << ptr2.use_count() << endl;

  auto ptr = make_shared<student>();
  ptr->setName("Jarvis");
  ptr->setMarks(98);

  cout << "Weak pointer output >>" << endl;
  weak_ptr<student> ptr3;
  ptr3 = ptr;
  cout << "Reference count of weak_ptr: " << ptr3.use_count() << endl;
  shared_ptr<student> ref = ptr3.lock();
  cout << ref->getName() << " : " << ref->getMarks() << "\n";

  return 0;
}

Ausgabe:

stark@stark:~/eclipse-workspace/smart_pointer$ g++ types_smart.cc
stark@stark:~/eclipse-workspace/smart_pointer$ ./a.out
unique_ptr output >>
Stark : 100
shared_ptr output >>
Tony : 99
Tony : 99
Reference count of shared_ptr: 2
Weak pointer output >>
Reference count of weak_ptr: 1
Jarvis : 98

Wir sollten beachten, dass die Referenzzählung des schwachen Zeigers 1 ist, da er nicht an der Referenzzählung teilnimmt.

Wann sollten Smart-Pointer in C++ verwendet werden?

Wir sollten intelligente Zeiger nicht für alles verwenden. Da wir eine Klassenvorlage für intelligente Zeiger verwenden, haben sie im Allgemeinen eine verminderte Leistung und tragen zur Komplexität des Programms bei.

Wir sollten rohe Zeiger für leistungskritische Anwendungen und kleine Programme als Best Practice verwenden. Wir sollten die Zeigerressourcen jedoch immer freigeben, nachdem ihre Verwendung beendet ist.

Wenn unser Code groß ist und es schwierig ist, Speicher für jeden Zeiger manuell freizugeben, sollten wir die intelligenten Zeiger verwenden. Wir sollten Smart Pointer bevorzugen, wenn die Anwendung nicht leistungskritisch ist.

Wenn wir Ausnahmen, Referenzzählungen und die Ausführung von Freigabezeiten behandeln müssen, sollten wir die intelligenten Zeiger verwenden.

Verwandter Artikel - C++ Pointer