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_ptrmacht 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 newSchlüsselwort vorzuziehen .

unique_ptr

C++11 führt ein std::unique_ptr, im Header definiert <memory>.

A unique_ptrist ein Container für einen Rohzeiger, den das besitzen unique_ptrsoll. A unique_ptrverhindert explizit das Kopieren seines enthaltenen Zeigers (wie es bei einer normalen Zuweisung passieren würde), aber die std::moveFunktion kann verwendet werden, um den Besitz des enthaltenen Zeigers auf einen anderen zu übertragen unique_ptr. A unique_ptrkann 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_ptrist unter C++11 veraltet und vollständig aus C++17 entfernt . Der Kopierkonstruktor und die Zuweisungsoperatoren von auto_ptrkopieren den gespeicherten Zeiger nicht wirklich. Stattdessen übertragen sie es und lassen das vorherige auto_ptrObjekt leer. Dies war eine Möglichkeit, einen strikten Besitz zu implementieren, sodass immer nur ein auto_ptrObjekt den Zeiger besitzen kann. Dies bedeutet, dass dies auto_ptrnicht verwendet werden sollte, wenn Kopiersemantik benötigt wird. Da er auto_ptrmit 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.

shared_ptr und schwach_ptr

C++11 führt std::shared_ptrund ein std::weak_ptr, die im Header definiert sind <memory>. C++11 führt auch ein std::make_shared( std::make_uniquewurde in C++14 eingeführt), um dynamischen Speicher im RAII- Paradigma sicher zuzuweisen .

A shared_ptrist 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_ptrzerstö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_ptrist ein Container für einen Rohzeiger. Es wird als Kopie einer shared_ptr. Das Vorhandensein oder die Vernichtung von weak_ptrKopien einer shared_ptrhat keine Auswirkungen auf die shared_ptroder ihre anderen Kopien. Nachdem alle Kopien von a shared_ptrzerstört wurden, werden alle weak_ptrKopien 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_ptrAnwendungen Referenzzählung , zirkuläre Referenzen sind potentiell ein Problem. Eine kreisförmige shared_ptrKette 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_ptrund weak_ptrObjekte zugreifen , die auf dasselbe Objekt zeigen.

Das referenzierte Objekt muss separat geschützt werden, um die Thread-Sicherheit zu gewährleisten .

shared_ptrund weak_ptrbasieren 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

Verweise

Weiterlesen

Externe Links