2118 Stimmen

Was ist ein intelligenter Zeiger und wann sollte ich einen verwenden?

Was ist ein Smart Pointer und wann sollte ich einen verwenden?

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/ViewF‌​eedback.aspx?Feedbac‌​kID=98871
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedb‌​ack.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

2036voto

Lloyd Punkte 22223

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.

7 Stimmen

Meinen Sie std::auto_ptr p1 (new MyObject()); anstelle von std::auto_ptr p1 (new Owner());?

1 Stimmen

Woher kommt der tr1-Namensraum? Ist es in der Standardbibliothek enthalten?

1 Stimmen

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

407voto

einpoklum Punkte 100527

Hier ist eine einfache Antwort für diese Tage des modernen C++ (C++11 und später):

  • "Was ist ein Smart Pointer?"
    Es handelt sich um einen Typ, dessen Werte wie Zeiger verwendet werden können, der jedoch die zusätzliche Funktion der automatischen Speicherverwaltung bietet: Wenn ein Smart Pointer nicht mehr verwendet wird, wird der von ihm zeigende Speicher freigegeben (siehe auch die ausführlichere Definition auf Wikipedia).
  • "Wann sollte ich einen verwenden?"
    In Code, der das Verfolgen des Besitzes eines Speicherbereichs, das Allozieren oder Deallozieren involviert; der Smart Pointer spart Ihnen oft die Notwendigkeit, diese Dinge explizit zu tun.
  • "Aber welchen Smart Pointer sollte ich in welchen Fällen verwenden?"
    • Verwenden Sie 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.
    • Verwenden Sie 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.
    • Verwenden Sie 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).
    • Es gibt einen Vorschlag, Hazard Pointer zu C++26 hinzuzufügen, aber im Moment haben Sie sie nicht.
    • Verwenden Sie nicht die boost:: Smart Pointer oder std::auto_ptr, außer in speziellen Fällen, über die Sie sich informieren können, wenn es sein muss.
  • "Hey, ich habe nicht gefragt, welchen ich verwenden soll!"
    Ach, aber das wollten Sie wirklich, geben Sie es zu.
  • "Also wann sollte ich reguläre Zeiger verwenden?"
    Hauptsächlich in Code, der dem Speicherbesitz gegenüber gleichgültig ist. Dies wäre typischerweise in Funktionen, die einen Zeiger von anderswo erhalten und weder allozieren noch deallozieren oder eine Kopie des Zeigers speichern, die über das Ende ihrer Ausführung hinausreicht.

14 Stimmen

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.

1 Stimmen

@wiktor.wandachowicz T* ist für std::unique_ptr das, was std::weak_ptr für std::shared_ptr ist.

3 Stimmen

@Caleth: Nein, das würde ich nicht sagen.

119voto

sergtk Punkte 10154

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:

  • Schutz vor Ausnahmen
  • Halter (Hinweis, std::auto_ptr ist die Implementierung eines solchen Typs von Smart Pointer)
  • Resource Acquisition Is Initialization (wird häufig für eine ausnahme-sichere Ressourcenverwaltung in C++ verwendet)
  • Haltergrenzen
  • Referenzzählung
  • Paralleler Zugriff auf den Zähler
  • Zerstörung und Freigabe von Speicher

6 Stimmen

Warnung std::auto_ptr ist veraltet und stark abgeraten, da Sie versehentlich die Eigentumsübertragung durchführen können. -- C++11 entfernt die Notwendigkeit von Boost, verwenden Sie: std::unique_ptr, std::shared_ptr und std::weak_ptr

46voto

Sridhar Iyer Punkte 2642

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.

34voto

markets Punkte 9034

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:

  • ... der Zeiger die Daten tatsächlich nicht besitzen sollte... d.h., wenn Sie nur auf die Daten zugreifen, diese jedoch nach der Funktion, in der Sie darauf verweisen, überleben sollen.
  • ... der Smart-Pointer selbst nicht irgendwann zerstört wird. Sie möchten nicht, dass er im Speicher bleibt, der nie zerstört wird (z. B. in einem Objekt, das dynamisch allokiert ist, aber nicht explizit gelöscht wird).
  • ... zwei Smart-Pointer auf dieselben Daten zeigen könnten. (Es gibt jedoch noch intelligentere Zeiger, die damit umgehen können... das nennt man Referenzzählung.)

Siehe auch:

CodeJaeger.com

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.

Powered by:

X