Deklaration und Verwendung von unique_ptr in C++
![Deklaration und Verwendung von unique_ptr in C++](/img/Cpp/feature-image---unique_ptr-cpp.webp)
Zeiger dienen in C++ vielen nützlichen Zwecken. Vom Platzsparen in Programmen bis hin zur Unterstützung bei der Implementierung von Datenstrukturen sind Zeiger ein sehr wichtiges Werkzeug für Programmiersprachen wie C und C++.
In diesem Artikel werden die Konzepte eines solchen Zeigers, des eindeutigen Zeigers, erörtert. Die Standardbibliothek von C++ stellt diesen Zeiger als eine der Implementierungen der intelligenten Zeiger bereit.
Sie fragen sich, was diese intelligenten Zeiger jetzt sind? Weiter lesen; Wir haben alles für Sie abgedeckt.
Deklaration und Verwendung von unique_ptr
in C++
Wir wissen bereits, dass Zeiger verwendet werden, um Adressen anderer Variablen zu speichern. Obwohl diese Hinweise an vielen Stellen nützlich sind, haben sie ein großes Problem.
Sehen Sie sich den angegebenen Codeblock an. Meistens deklarieren wir einen Zeiger wie folgt und verwenden ihn dann, löschen ihn aber nie.
void demo() {
int *d = new int;
*d = 10;
}
Dies führt zu dem Problem von Speicherlecks. Es tritt auf, wenn ein Speicherabschnitt nach der Verwendung nicht freigegeben oder freigegeben wird.
Somit führt der für kein anderes Programm verfügbarer oder freier Speicher zu Speicherplatzverschwendung und bremst die Leistung. Das ist nichts anderes als ein Speicherleck.
Bei komplexeren Programmen kann dies sogar dazu führen, dass der gesamte Heap-Speicher unbrauchbar wird. Daher hat C++11 das Konzept der intelligenten Zeiger entwickelt, um diesem Problem zu begegnen.
Intelligente Zeiger in C++
Sprachen wie Java und C# haben einen Mechanismus, durch den der ungenutzte Speicher automatisch freigegeben wird. Der Programmierer muss also keine Zeiger freigeben, worum sich das Garbage-Collection-System kümmert.
Für C++ wird dies durch die Verwendung von intelligenten Zeigern erreicht. Intelligente Zeiger sind nichts anderes als einfache Klassen, die die rohen Zeiger umschließen und Operatoren wie *
und ->
überladen.
Die Objekte einer Smart-Pointer-Klasse scheinen die gleichen wie die normalen Pointer zu sein, aber im Gegensatz zu normalen Pointern können sie selbst den unbenutzten Speicherplatz freigeben. Vier Arten von intelligenten Zeigern sind innerhalb des <memory>
-Headers der Standardbibliothek von C++ definiert, von denen unique_ptr
einer ist.
Jetzt sind wir bereit, etwas über den eindeutigen Zeiger von C++ zu lernen.
Eindeutige Zeiger in C++
Ein unique_ptr
wurde entwickelt, um den auto_ptr
von C++ zu ersetzen. Im Gegensatz zu Raw-Zeigern ist unique_ptr
ein Container für Raw-Zeiger und garantiert exklusives Eigentum und kein Speicherleck.
Deklaration von unique_ptr
in C++
Der folgende Code zeigt, wie wir einen eindeutigen Zeiger in C++ deklarieren.
std::unique_ptr<Type> p(new Type);
Wenn wir hier einen eindeutigen Zeiger erstellen wollen, der auf eine Ganzzahl zeigt, sagen wir 10
, dann würde sich die obige Syntax wie folgt ändern.
std::unique_ptr<int> p(new int(10));
Die obige Codezeile bedeutet, dass ein eindeutiger Zeiger, p
, auf den Wert 10
zeigt. Sie müssen verstehen, wie diese grundlegende Deklaration funktioniert.
Der Ausdruck new
erzeugt den Zeiger p
in dieser obigen Codezeile. Aber die Bedeutung ändert sich, wenn wir dieselbe Deklaration in die unten angegebene ändern.
std::unique_ptr<int> p2(x);
Hier wird nun der Pointer in der Variablen x
gespeichert. Konzeptionell sind die Dinge gleich.
Wir machen an beiden Stellen einen unique_ptr
, aber die zweite Methode ist nicht ratsam.
Mit der zweiten Methode können wir so etwas tun:
std::unique_ptr<int> p2(x);
std::unique_ptr<int> p3(x);
//....
std::unique_ptr<int> p6(x);
Dies bedeutet, dass mehr als ein eindeutiger Zeiger dasselbe Objekt besitzt, was gegen die Semantik eines eindeutigen Zeigers verstößt, wie Sie in den folgenden Abschnitten sehen werden.
Außerdem haben wir in C++14 die Möglichkeit, mit make_unique
einen unique_ptr
wie folgt zu deklarieren:
unique_ptr<int> d = make_unique<int>(10);
Dadurch entsteht ein eindeutiger Zeiger d
, der auf den Wert 10
zeigt.
Betrachten wir nun nacheinander die verschiedenen Features des unique_ptr
.
Automatisches Löschen des C++ unique_ptr
Bei rohen Zeigern vergessen Programmierer oft, den Zeiger nach der Verwendung zu löschen, was zu dem Problem des Speicherlecks führt. Der unique_ptr
erspart diesen Ärger, da er das Objekt zerstört, das er enthält, wenn der unique_ptr
den Gültigkeitsbereich verlässt.
Das Objekt wird zerstört, wenn der unique_ptr
zerstört wird oder dem unique_ptr
ein anderer Zeiger zugewiesen wird. Dies geschieht mit Hilfe von get_deleter() (ptr)
, das den delete
-Operator verwendet, um das Objekt zu zerstören und den Speicher freizugeben.
Wenn Sie also einen unique_ptr
verwenden, müssen Sie dynamisch zugewiesene Objekte nicht löschen, und der Destruktor von unique_ptr
kümmert sich darum.
Unten ist ein illustrierter Code, der den Bereich zeigt, außerhalb dessen der Zeiger zerstört wird.
void demo() { unique_ptr<int> demo(new int); }
< -- -- -- -- -- -- -- -- -- -- -- -- -scope of the pointer ends here
Das Objekt wird zerstört, wenn das Steuerelement diese geschweiften Klammern verlässt.
Exklusives Eigentum an C++ unique_ptr
Das nächste und sehr wichtige Feature von unique_ptr
bietet exklusives Eigentum an einem Objekt und kann nicht kopiert werden. Das bedeutet, dass nur ein unique_ptr
gleichzeitig auf nur ein Objekt zeigen kann.
Aus diesem Grund können wir auch keinen unique_ptr
kopieren. Bei rohen Zeigern führt die normale Zuweisung häufig zum Kopieren des Zeigers.
Bei unique_ptr
ist dies jedoch nicht der Fall. Sehen Sie sich dieses Beispiel an.
Hier haben wir zwei eindeutige Zeiger, p1
und p2
, und wir kopieren oder weisen den ersten Zeiger dem zweiten zu.
#include <iostream>
#include <memory>
using namespace std;
int main() {
unique_ptr<int> p1(new int);
unique_ptr<int> p2 = p1;
cout << "Success";
return 0;
}
Ausgang:
use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]'
8 | unique_ptr<int> p2 = p1;
Sie können sehen, dass wir einen Kompilierzeitfehler erhalten und die Druckanweisung nicht ausgeführt wird. Dies zeigt deutlich, dass wir in C++ keinen unique_ptr
kopieren können, der gleichzeitig nur auf ein Objekt zeigt.
Hätten wir dies nun mit einem Rohzeiger gemacht, wäre die print
-Anweisung erfolgreich ausgeführt worden. Dies geschieht unten.
#include <iostream>
using namespace std;
int main()
{
int *ptr1;
int *ptr2;
ptr2 = ptr1;
cout << "Success";
return 0;
}
Ausgang:
Success
Diesmal werden die Zeiger kopiert, da es sich um Rohzeiger handelt. So funktioniert unique_ptr
im Wesentlichen, wenn es um Duplikation geht.
Eigentumsübertragung des C++ unique_ptr
Wie zuvor gesehen, können wir den Besitz eines unique_ptr
nicht ändern, indem wir ihn einfach kopieren. Die Lösung hier ist die Verwendung der Funktion move()
, die von der Standardbibliothek von C++ bereitgestellt wird.
Wir nehmen denselben Code, bei dem wir versucht haben, ein unique_ptr
mit dem Zuweisungsoperator zu kopieren. Diesmal verwenden wir bei dieser Zuweisung jedoch die Funktion move()
.
#include <iostream>
#include <memory>
using namespace std;
int main() {
unique_ptr<int> p1(new int);
unique_ptr<int> p2 = move(p1); // using the move() function
cout << "Success";
return 0;
}
Ausgang:
Success
Da wir nun die Funktion move()
verwendet haben, erhalten wir keinen Fehler und die Anweisung print
wird ebenfalls ausgeführt.
Auf diese Weise können wir das Eigentum an einem Objekt auf einen anderen unique_ptr
übertragen, aber in jedem Fall bleibt das Objekt, auf das ein unique_ptr
zeigt, eines nach dem anderen.
Dies waren die Hauptmerkmale des unique_ptr
, die sie von rohen Zeigern unterscheiden.
Weitere Informationen zu eindeutigen Zeigern finden Sie in dieser Dokumentation.
Abschluss
In diesem Artikel sind wir das Konzept des eindeutigen Zeigers in C++ durchgegangen. Ein eindeutiger Zeiger ist ein intelligenter Zeiger, der für seine exklusiven Eigentums- und automatischen Löschfunktionen bekannt ist.
Wir haben gelernt, wie es deklariert wird und wie wir trotzdem den Besitz eines eindeutigen Zeigers mit der Funktion move()
übertragen können. Ich hoffe, Sie können den eindeutigen Zeiger bei Bedarf verwenden, nachdem Sie ihre Funktionen kennen.