Was ist ein Smart Pointer und wann sollte ich einen verwenden?
Meinen Sie std::auto_ptr p1 (new MyObject());
anstelle von std::auto_ptr p1 (new Owner());
?
Was ist ein Smart Pointer und wann sollte ich einen verwenden?
UPDATE
Diese Antwort ist ziemlich alt und beschreibt daher, was damals als 'gut' galt, nämlich die intelligenten Zeiger, die von der Boost-Bibliothek bereitgestellt wurden. Seit C++11 hat die Standardbibliothek ausreichende Typen von intelligenten Zeigern bereitgestellt, daher sollten Sie die Verwendung von std::unique_ptr
, std::shared_ptr
und std::weak_ptr
bevorzugen.
Es gab auch std::auto_ptr
. Es war sehr ähnlich einem begrenzten Zeiger, außer dass es auch die "besondere" gefährliche Fähigkeit hatte, kopiert zu werden - was auch unerwartet den Besitz überträgt.
Es wurde in C++11 veraltet und in C++17 entfernt, daher sollten Sie es nicht verwenden.
std::auto_ptr p1 (new MyObject());
std::auto_ptr p2 = p1; // Kopieren und Besitz übertragen.
// p1 wird geleert!
p2->DoSomething(); // Funktioniert.
p1->DoSomething(); // Oh oh. Hoffentlich wirft es eine NULL-Zeiger-Ausnahme.
ALTEN ANTWORT
Ein intelligenter Zeiger ist eine Klasse, die einen 'rohen' (oder 'nackten') C++-Zeiger umschließt, um die Lebensdauer des damit verbundenen Objekts zu verwalten. Es gibt keinen einzelnen intelligenten Zeigertyp, aber alle versuchen, einen rohen Zeiger auf praktische Weise abzubilden.
Intelligente Zeiger sollten gegenüber rohen Zeigern bevorzugt werden. Wenn Sie das Gefühl haben, Zeiger verwenden zu müssen (überlegen Sie zuerst, ob Sie es wirklich müssen), sollten Sie normalerweise einen intelligenten Zeiger verwenden, da dies viele Probleme mit rohen Zeigern verringern kann, hauptsächlich das Vergessen, das Objekt zu löschen und Speicher freizugeben.
Bei rohen Zeigern muss der Programmierer das Objekt explizit zerstören, wenn es nicht mehr nützlich ist.
// Objekt erstellen, um ein Ziel zu erreichen
MyObject* ptr = new MyObject();
ptr->DoSomething(); // Verwenden des Objekts in irgendeiner Weise
delete ptr; // Objekt zerstören. Fertig damit.
// Moment mal, was ist, wenn DoSomething() eine Ausnahme auslöst...?
Im Vergleich dazu definiert ein intelligenter Zeiger eine Richtlinie, wann das Objekt zerstört werden soll. Sie müssen das Objekt immer noch erstellen, müssen sich aber nicht mehr um die Zerstörung kümmern.
SomeSmartPtr ptr(new MyObject());
ptr->DoSomething(); // Objekt auf irgendeine Weise verwenden.
// Die Zerstörung des Objekts erfolgt abhängig
// von der Richtlinie, die die Klasse des intelligenten Zeigers verwendet.
// Selbst wenn DoSomething() eine Ausnahme auslöst, wird die Zerstörung erfolgen
Die einfachste Richtlinie besteht darin, dass der intelligente Zeiger die Gültigkeit des Objekts umschließt, wie es von boost::scoped_ptr
oder std::unique_ptr
implementiert wird.
void f()
{
{
std::unique_ptr ptr(new MyObject());
ptr->DoSomethingUseful();
} // ptr läuft aus dem Gültigkeitsbereich --
// das MyObject wird automatisch zerstört.
// ptr->Oops(); // Kompilierfehler: "ptr" nicht definiert
// da es nicht mehr im Gültigkeitsbereich ist.
}
Beachten Sie, dass std::unique_ptr
-Instanzen nicht kopiert werden können. Dadurch wird verhindert, dass der Zeiger mehrmals (falsch) gelöscht wird. Sie können jedoch Referenzen dazu an andere von Ihnen aufgerufene Funktionen übergeben.
std::unique_ptr
sind nützlich, wenn Sie die Lebensdauer des Objekts an einen bestimmten Codeblock binden möchten oder wenn Sie es als Datenelement in einem anderen Objekt eingebettet haben, die Lebensdauer dieses anderen Objekts. Das Objekt existiert, bis der umgebende Codeblock verlassen wird oder bis das enthaltende Objekt selbst zerstört wird.
Eine komplexere Richtlinie für intelligente Zeiger beinhaltet das Zählen von Referenzen des Zeigers. Dies ermöglicht, dass der Zeiger kopiert wird. Wenn die letzte "Referenz" zum Objekt zerstört wird, wird das Objekt gelöscht. Diese Richtlinie wird von boost::shared_ptr
und std::shared_ptr
implementiert.
void f()
{
typedef std::shared_ptr MyObjectPtr; // nette Kurzform
MyObjectPtr p1; // Leer
{
MyObjectPtr p2(new MyObject());
// Es gibt jetzt eine "Referenz" auf das erstellte Objekt
p1 = p2; // Zeiger kopieren.
// Es gibt jetzt zwei Referenzen auf das Objekt.
} // p2 wird zerstört, sodass eine Referenz auf das Objekt verbleibt.
} // p1 wird zerstört, sodass eine Referenzzählung von Null bleibt.
// Das Objekt wird gelöscht.
Referenzzählende Zeiger sind sehr nützlich, wenn die Lebensdauer Ihres Objekts viel komplizierter ist und nicht direkt an einen bestimmten Codeabschnitt oder an ein anderes Objekt gebunden ist.
Es gibt einen Nachteil bei referenzzählenden Zeigern — die Möglichkeit, eine hängende Referenz zu erzeugen:
// Erstellen des intelligenten Zeigers auf dem Heap
MyObjectPtr* pp = new MyObjectPtr(new MyObject())
// Hmm, wir haben vergessen, den intelligenten Zeiger zu zerstören,
// daher wird das Objekt nie zerstört!
Eine weitere Möglichkeit besteht darin, kreisförmige Verweise zu erstellen:
struct Owner {
std::shared_ptr other;
};
std::shared_ptr p1 (new Owner());
std::shared_ptr p2 (new Owner());
p1->other = p2; // p1 verweist auf p2
p2->other = p1; // p2 verweist auf p1
// Oh, die Referenzzählung von p1 und p2 geht nie auf Null!
// Die Objekte werden nie zerstört!
Um dieses Problem zu umgehen, haben sowohl Boost als auch C++11 einen weak_ptr
definiert, um eine schwache (ungezählte) Referenz zu einem shared_ptr
zu definieren.
Meinen Sie std::auto_ptr p1 (new MyObject());
anstelle von std::auto_ptr p1 (new Owner());
?
Vielleicht eine Kleinigkeit in einem ansonsten erstaunlichen Kommentar, aber es wäre großartig, wenn dies auch etwas über den Speicher- und CPU-Overhead, der durch einen Intelligenten Zeiger verursacht wird, beinhalten würde; damit kann wirklich gesagt werden, dass alle Informationen enthalten sind
Hier ist eine einfache Antwort für diese Tage des modernen C++ (C++11 und später):
std::unique_ptr
, wenn Sie möchten, dass Ihr Objekt nur so lange existiert, wie eine einzige eigene Referenz darauf besteht. Verwenden Sie es zum Beispiel für einen Zeiger auf Speicher, der beim Betreten eines bestimmten Bereichs alloziert wird und beim Verlassen des Bereichs dealloziert wird.std::shared_ptr
, wenn Sie möchten, dass auf Ihr Objekt von mehreren Stellen aus zugegriffen wird - und nicht möchten, dass Ihr Objekt dealloziert wird, solange all diese Referenzen selbst noch existieren.std::weak_ptr
, wenn Sie möchten, dass auf Ihr Objekt von mehreren Stellen aus zugegriffen wird - für die Referenzen, bei denen es in Ordnung ist, sie zu ignorieren und zu deallozieren (sie werden also nur feststellen, dass das Objekt weg ist, wenn Sie versuchen, darauf zuzugreifen).boost::
Smart Pointer oder std::auto_ptr
, außer in speziellen Fällen, über die Sie sich informieren können, wenn es sein muss.
Es ist erwähnenswert, dass während intelligente (besitzende) Zeiger beim ordnungsgemäßen Speichermanagement helfen, rohe (nicht besitzende) Zeiger immer noch für andere Organisationszwecke in Datenstrukturen nützlich sind. Herb Sutter hielt dazu eine großartige Präsentation auf der CppCon 2016, die Sie auf YouTube sehen können: Leak-Freedom in C++... By Default.
@wiktor.wandachowicz T*
ist für std::unique_ptr
das, was std::weak_ptr
für std::shared_ptr
ist.
UPDATE:
Diese Antwort ist veraltet bezüglich der C++-Typen, die in der Vergangenheit verwendet wurden.
std::auto_ptr
ist veraltet und in neuen Standards entfernt.
Anstelle von boost::shared_ptr
sollte std::shared_ptr
verwendet werden, der Teil des Standards ist.
Die Links zu den Konzepten hinter der Begründung von Smart Pointern sind größtenteils immer noch relevant.
Modernes C++ hat die folgenden Smart-Pointer-Typen und erfordert keine boost Smart Pointer:
Es gibt auch eine 2. Ausgabe des in der Antwort erwähnten Buches: C++ Templates: The Complete Guide 2nd Edition von David Vandevoorde Nicolai, M. Josuttis, Douglas Gregor
ALTE ANTWORT:
Ein Smart Pointer ist ein pointer-ähnlicher Typ mit zusätzlicher Funktionalität wie automatischer Speicherdeallokation, Referenzzählen usw.
Eine kurze Einführung finden Sie auf der Seite Smart Pointers - Was, Warum, Welche?.
Einer der einfachen Smart-Pointer-Typen ist std::auto_ptr
(Kapitel 20.4.5 des C++-Standards), der es ermöglicht, Speicher automatisch freizugeben, wenn er außerhalb des Geltungsbereichs liegt und der robuster ist als die Verwendung einfacher Zeiger, wenn Ausnahmen auftreten, jedoch weniger flexibel.
Ein weiterer praktischer Typ ist boost::shared_ptr
, der das Referenzzählen implementiert und Speicher automatisch freigibt, wenn keine Verweise auf das Objekt mehr vorhanden sind. Dies hilft, Speicherlecks zu vermeiden und ist einfach zu verwenden, um RAII zu implementieren.
Das Thema wird ausführlich im Buch "C++ Templates: The Complete Guide" von David Vandevoorde, Nicolai M. Josuttis behandelt, Kapitel Kapitel 20. Smart Pointer. Einige behandelte Themen:
Die von Chris, Sergdev und Llyod bereitgestellten Definitionen sind korrekt. Ich bevorzuge jedoch eine einfachere Definition, um mein Leben einfach zu halten: Ein intelligenter Zeiger ist einfach eine Klasse, die die ->
und *
Operatoren überlädt. Das bedeutet, dass Ihr Objekt semantisch wie ein Zeiger aussieht, aber Sie können es dazu bringen, viel coolere Dinge zu tun, einschließlich Referenzzählung, automatische Zerstörung usw. shared_ptr
und auto_ptr
sind in den meisten Fällen ausreichend, bringen jedoch ihre eigenen kleinen Eigenheiten mit sich.
Ein Smart-Pointer ist wie ein regulärer (typisierter) Zeiger, wie z. B. "char*", außer wenn der Zeiger selbst den Gültigkeitsbereich verlässt, wird auch das, worauf er zeigt, gelöscht. Sie können ihn wie einen regulären Zeiger verwenden, indem Sie "->" verwenden, jedoch nicht, wenn Sie einen tatsächlichen Zeiger auf die Daten benötigen. Dafür können Sie "&*ptr" verwenden.
Es ist nützlich für:
Objekte, die mit new allokiert werden müssen, aber deren Lebensdauer identisch mit der eines Objekts auf einem Stack sein soll. Wenn das Objekt einem Smart-Pointer zugewiesen wird, wird es gelöscht, wenn das Programm diese Funktion/diesen Block verlässt.
Datenelemente von Klassen, sodass beim Löschen des Objekts alle im Besitz befindlichen Daten ebenfalls gelöscht werden, ohne speziellen Code im Destruktor (Sie müssen sicherstellen, dass der Destruktor virtuell ist, was fast immer eine gute Sache ist).
Sie möchten möglicherweise keinen Smart-Pointer verwenden, wenn:
Siehe auch:
CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.
7 Stimmen
Überprüfen Sie diese Frage:
Smart Pointer: Oder wer besitzt dich Baby
2 Stimmen
Beachten Sie, dass die Implementierung von std::auto_ptr in Visual Studio 2005 schrecklich kaputt ist.
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=98871
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=101842 Verwenden Sie stattdessen die boost ones.
32 Stimmen
Zwei ausgezeichnete Artikel zum Thema: - Smart Pointer - Was, Warum, Welche? - Guru der Woche Nr. 25
1 Stimmen
Hier ist Alexandrescus (kostenloses) Kapitel zur Feinheiten bei der Erstellung von intelligenten Zeigern verschiedener Varianten: informit.com/articles/article.aspx?p=31529 In seiner Implementierung verwendet er Template-Argumente als "Policies", um anzugeben, welche Attribute er möchte (z.B. Verweiszählung), während die Standardbibliothek separate Klassen verwendet. Beachten Sie, dass er auch geschrieben hat, bevor Rvalue-Verweise verfügbar waren, um etwas wie std::unique_ptr möglich zu machen.
0 Stimmen
Ich möchte noch einen weiteren Punkt zu der obigen Frage hinzufügen, der Smart-Pointer std::shared_ptr hat keinen Indizoperator und unterstützt keine Pointer-Arithmetik, wir können get() verwenden, um einen integrierten Zeiger zu erhalten.
0 Stimmen
Es wurde in C++17 hinzugefügt, ist jedoch nicht garantiert, dass es für alles außer einfachen Arrays funktioniert.