Intelligenter Zeiger - Smart pointer
In der Informatik ist ein intelligenter Zeiger ein abstrakter Datentyp , der einen Zeiger simuliert und gleichzeitig zusätzliche Funktionen wie automatische Speicherverwaltung oder Grenzüberprüfung bietet . Solche Funktionen sollen Fehler reduzieren, die durch den Missbrauch von Zeigern verursacht werden, während die Effizienz erhalten bleibt. Intelligente Zeiger verfolgen normalerweise den Speicher, auf den sie verweisen, und können auch verwendet werden, um andere Ressourcen wie Netzwerkverbindungen und Dateihandles zu verwalten. Intelligente Zeiger wurden erstmals in der ersten Hälfte der 1990er Jahre in der Programmiersprache C++ populär, um die Kritik an der fehlenden automatischen Garbage Collection von C++ zu widerlegen .
Der Missbrauch von Zeigern kann eine Hauptquelle für Fehler sein. Intelligente Zeiger verhindern die meisten Situationen von Speicherlecks, indem sie die Speicherfreigabe automatisch vornehmen. Allgemeiner machen sie die Objektzerstörung automatisch: Ein von einem intelligenten Zeiger gesteuertes Objekt wird automatisch zerstört ( abgeschlossen und dann freigegeben), wenn der letzte (oder einzige) Eigentümer eines Objekts zerstört wird, zum Beispiel weil der Eigentümer eine lokale Variable ist, und Ausführung verlässt den Gültigkeitsbereich der Variablen . Intelligente Zeiger eliminieren auch baumelnde Zeiger, indem sie die Zerstörung aufschieben, bis ein Objekt nicht mehr verwendet wird.
Wenn eine Sprache die automatische Garbage Collection unterstützt (z. B. Java oder C# ), werden intelligente Zeiger für die Rückforderungs- und Sicherheitsaspekte der Speicherverwaltung nicht benötigt, sind jedoch für andere Zwecke nützlich, wie z. B. die Verwaltung von Cache- Datenstrukturen und Ressourcenverwaltung von Objekten wie Datei-Handles oder Netzwerk-Sockets .
Es gibt mehrere Arten von intelligenten Zeigern. Einige arbeiten mit Referenzzählung , andere, indem sie einem Zeiger den Besitz eines Objekts zuweisen.
Geschichte
Obwohl C++ das Konzept der intelligenten Zeiger populär gemacht hat, insbesondere die referenzgezählte Variante, hatte der unmittelbare Vorgänger einer der Sprachen, die das Design von C++ inspirierten, referenzgezählte Referenzen in die Sprache eingebaut. C++ wurde teilweise von Simula67 inspiriert. Der Vorfahr von Simula67 war Simula I. Insofern das Element von Simula I dem Zeiger von C++ ohne null analog ist , und insofern der Prozess von Simula I mit einer Dummy-Anweisung als sein Aktivitätskörper analog der Struktur von C++ ist (die selbst dem Datensatz von CAR Hoare in damals analog ist) -zeitgenössische Arbeit der 1960er Jahre) hatte Simula I gezählte Elemente (dh Zeigerausdrücke, die Indirektion enthalten) auf Prozesse (dh Datensätze) spätestens im September 1965, wie in den zitierten Abschnitten unten gezeigt.
Prozesse können einzeln referenziert werden. Physikalisch ist eine Prozessreferenz ein Zeiger auf einen Speicherbereich, der die lokalen Daten des Prozesses und einige zusätzliche Informationen enthält, die seinen aktuellen Ausführungszustand definieren. Aus den in Abschnitt 2.2 genannten Gründen sind Prozessreferenzen jedoch immer indirekt, durch Elemente, die als Elemente bezeichnet werden. Formal ist eine Referenz auf einen Prozess der Wert eines Ausdrucks vom Typ element .
...
Elementwerte können durch Zuweisungen und Verweise auf gespeichert und abgerufen werden Elementgrößen und mit anderen Mitteln.
Die Sprache enthält einen Mechanismus, um die Attribute eines Prozesses von außen, dh von innerhalb anderer Prozesse, zugänglich zu machen. Dies wird als Fernzugriff bezeichnet. Ein Prozess ist somit eine referenzierbare Datenstruktur.
Bemerkenswert ist die Ähnlichkeit zwischen einem Prozess, dessen Aktivitätskörper ein Dummy-Statement ist, und dem kürzlich von CAR Hoare und N. Wirth . vorgeschlagenen Datensatzkonzept
Da C++ Simulas Ansatz zur Speicherzuweisung übernommen hat – das neue Schlüsselwort bei der Zuweisung eines Prozesses/Datensatzes, um diesem Prozess/Datensatz ein neues Element zu erhalten – ist es nicht verwunderlich, dass C++ schließlich Simulas referenzzählenden Smart-Pointer-Mechanismus innerhalb des Elements als wiederbelebt hat Gut.
Merkmale
In C++ ist ein Smart Pointer als Template-Klasse implementiert, die durch Operatorüberladung das Verhalten eines traditionellen (rohen) Pointers (zB Dereferenzierung, Zuweisung) nachahmt und gleichzeitig zusätzliche Speicherverwaltungsfunktionen bereitstellt.
Intelligente Zeiger können eine absichtliche Programmierung erleichtern, indem sie im Typ ausdrücken, wie der Speicher des Referenzpunkts des Zeigers verwaltet wird. Wenn beispielsweise eine C++-Funktion einen Zeiger zurückgibt, gibt es keine Möglichkeit zu wissen, ob der Aufrufer den Speicher des Referenten löschen soll, wenn der Aufrufer mit den Informationen fertig ist.
SomeType* AmbiguousFunction(); // What should be done with the result?
Traditionell wurden Namenskonventionen verwendet, um die Mehrdeutigkeit aufzulösen, was ein fehleranfälliger und arbeitsintensiver Ansatz ist. C++11 führte in diesem Fall eine Möglichkeit ein, die korrekte Speicherverwaltung sicherzustellen, indem die Funktion deklariert wurde, dass sie a zurückgibt unique_ptr
,
std::unique_ptr<SomeType> ObviousFunction();
Die Deklaration des Rückgabetyps der Funktion als a unique_ptr
macht deutlich, dass der Aufrufer das Ergebnis übernimmt, und die C++-Laufzeit sorgt dafür, dass der Speicher automatisch zurückgefordert wird. Vor C++11 kann unique_ptr durch auto_ptr ersetzt werden .
Neue Objekte erstellen
Um die Zuordnung von a . zu erleichtern
std::shared_ptr<SomeType>
C++11 eingeführt:
auto s = std::make_shared<SomeType>(constructor, parameters, here);
und ähnlich
std::unique_ptr<some_type>
Seit C++14 kann man verwenden:
auto u = std::make_unique<SomeType>(constructor, parameters, here);
Es wird in fast allen Fällen bevorzugt, diese Einrichtungen dem new
Schlüsselwort vorzuziehen .
unique_ptr
C++11 führt ein std::unique_ptr
, im Header definiert <memory>
.
A unique_ptr
ist ein Container für einen Rohzeiger, den das besitzen unique_ptr
soll. A unique_ptr
verhindert explizit das Kopieren seines enthaltenen Zeigers (wie es bei einer normalen Zuweisung passieren würde), aber die std::move
Funktion kann verwendet werden, um den Besitz des enthaltenen Zeigers auf einen anderen zu übertragen unique_ptr
. A unique_ptr
kann nicht kopiert werden, da sein Kopierkonstruktor und seine Zuweisungsoperatoren explizit gelöscht werden.
std::unique_ptr<int> p1(new int(5));
std::unique_ptr<int> p2 = p1; // Compile error.
std::unique_ptr<int> p3 = std::move(p1); // Transfers ownership. p3 now owns the memory and p1 is set to nullptr.
p3.reset(); // Deletes the memory.
p1.reset(); // Does nothing.
std::auto_ptr
ist unter C++11 veraltet und vollständig aus C++17 entfernt . Der Kopierkonstruktor und die Zuweisungsoperatoren von auto_ptr
kopieren den gespeicherten Zeiger nicht wirklich. Stattdessen übertragen sie es und lassen das vorherige auto_ptr
Objekt leer. Dies war eine Möglichkeit, einen strikten Besitz zu implementieren, sodass immer nur ein auto_ptr
Objekt den Zeiger besitzen kann. Dies bedeutet, dass dies auto_ptr
nicht verwendet werden sollte, wenn Kopiersemantik benötigt wird. Da er auto_ptr
mit seiner Kopiersemantik bereits vorhanden war, konnte er nicht zu einem Nur-Verschieben-Zeiger aufgerüstet werden, ohne die Abwärtskompatibilität mit bestehendem Code zu beeinträchtigen.
C++11 führt std::shared_ptr
und ein std::weak_ptr
, die im Header definiert sind <memory>
. C++11 führt auch ein std::make_shared
( std::make_unique
wurde in C++14 eingeführt), um dynamischen Speicher im RAII- Paradigma sicher zuzuweisen .
A shared_ptr
ist ein Container für einen Rohzeiger . Es unterhält Referenzzählung Eigentum an den darin enthaltenen Zeiger in Zusammenarbeit mit allen Kopien der shared_ptr
. Ein Objekt, auf das durch den enthaltenen Rohzeiger verwiesen wird, wird zerstört, wenn und nur wenn alle Kopien von shared_ptr
zerstört wurden.
std::shared_ptr<int> p0(new int(5)); // Valid, allocates 1 integer and initialize it with value 5.
std::shared_ptr<int[]> p1(new int[5]); // Valid, allocates 5 integers.
std::shared_ptr<int[]> p2 = p1; // Both now own the memory.
p1.reset(); // Memory still exists, due to p2.
p2.reset(); // Frees the memory, since no one else owns the memory.
A weak_ptr
ist ein Container für einen Rohzeiger. Es wird als Kopie einer shared_ptr
. Das Vorhandensein oder die Vernichtung von weak_ptr
Kopien einer shared_ptr
hat keine Auswirkungen auf die shared_ptr
oder ihre anderen Kopien. Nachdem alle Kopien von a shared_ptr
zerstört wurden, werden alle weak_ptr
Kopien leer.
std::shared_ptr<int> p1 = std::make_shared<int>(5);
std::weak_ptr<int> wp1 {p1}; // p1 owns the memory.
{
std::shared_ptr<int> p2 = wp1.lock(); // Now p1 and p2 own the memory.
// p2 is initialized from a weak pointer, so you have to check if the
// memory still exists!
if (p2) {
DoSomethingWith(p2);
}
}
// p2 is destroyed. Memory is owned by p1.
p1.reset(); // Free the memory.
std::shared_ptr<int> p3 = wp1.lock();
// Memory is gone, so we get an empty shared_ptr.
if (p3) { // code will not execute
ActionThatNeedsALivePointer(p3);
}
Da die Implementierung von shared_ptr
Anwendungen Referenzzählung , zirkuläre Referenzen sind potentiell ein Problem. Eine kreisförmige shared_ptr
Kette kann unterbrochen werden, indem der Code geändert wird, sodass eine der Referenzen eine weak_ptr
.
Mehrere Threads können sicher gleichzeitig auf verschiedene shared_ptr
und weak_ptr
Objekte zugreifen , die auf dasselbe Objekt zeigen.
Das referenzierte Objekt muss separat geschützt werden, um die Thread-Sicherheit zu gewährleisten .
shared_ptr
und weak_ptr
basieren auf Versionen, die von den Boost-Bibliotheken verwendet werden . C++ Technical Report 1 (TR1) führte sie zuerst als allgemeine Dienstprogramme in den Standard ein, aber C++11 fügt weitere Funktionen hinzu, entsprechend der Boost-Version.
Siehe auch
- Automatische Referenzzählung
- Ressourcenbeschaffung ist Initialisierung (RAII)
- auto_ptr
- Undurchsichtiger Zeiger
- Referenz (Informatik)
- Boost (C++-Bibliotheken)
- Fetter Zeiger
- Müllabfuhr in der Computerprogrammierung
Verweise
Weiterlesen
- Scott Meyer (2014). Effektives modernes C++ . Sebastopol, CA: O'Reilly Media . ISBN 978-1491903995. OCLC 884480640 .
Externe Links
- Intelligente Zeiger . Modernes C++-Design : Generische Programmierung und Designmuster, angewandt von Andrei Alexandrescu , Addison-Wesley, 2001.
- countptr.hpp . Die C++-Standardbibliothek - Ein Tutorial und eine Referenz von Nicolai M. Josuttis
- Boost Smart Pointer
- Das neue C++: Smart(er)-Zeiger . Herb Sutter 1. August 2002
- Smart Pointer - Was, Warum, Welches? . Yonat Sharon
- Übersicht über intelligente Zeiger . John M. Dlugosz
- Intelligente Zeiger in Delphi
- Intelligente Zeiger in Rust
- Intelligente Zeiger in modernem C++