Intelligente Zeiger: Wem gehört das Objekt? [geschlossen]


114

In C ++ dreht sich alles um Speicherbesitz - auch bekannt als Besitzersemantik .

Es liegt in der Verantwortung des Besitzers eines Teils des dynamisch zugewiesenen Speichers, diesen Speicher freizugeben. Die Frage wird also wirklich, wem die Erinnerung gehört.

In C ++ Eigentum dokumentiert wird durch die Art eines roh ist Zeiger in so in einem guten (IMO) C ++ Programm ist es sehr selten (gewickelte selten , nicht nie ) um rohe Zeiger zu sehen weitergegeben (wie rohe Zeiger nicht so Eigentum geschlossen haben , können wir nicht sagen, wem der Speicher gehört, und daher können Sie ohne sorgfältiges Lesen der Dokumentation nicht sagen, wer für den Besitz verantwortlich ist).

Umgekehrt ist es selten, dass Rohzeiger in einer Klasse gespeichert werden. Jeder Rohzeiger wird in einem eigenen Smart-Pointer-Wrapper gespeichert. ( NB: Wenn Sie kein Objekt besitzen, sollten Sie es nicht speichern, da Sie nicht wissen können, wann es außerhalb des Gültigkeitsbereichs liegt und zerstört wird.)

Also die Frage:

  • Auf welche Art von Besitzsemantik sind Menschen gestoßen?
  • Welche Standardklassen werden verwendet, um diese Semantik zu implementieren?
  • In welchen Situationen finden Sie sie nützlich?

Behalten wir 1 Art semantischen Eigentums pro Antwort bei, damit sie einzeln nach oben und unten abgestimmt werden können.

Zusammenfassung:

Konzeptionell sind intelligente Zeiger einfach und eine naive Implementierung einfach. Ich habe viele versuchte Implementierungen gesehen, aber sie sind immer auf eine Weise kaputt, die für gelegentliche Verwendung und Beispiele nicht offensichtlich ist. Daher empfehle ich, immer gut getestete Smart Pointer aus einer Bibliothek zu verwenden, anstatt Ihre eigenen zu rollen. std::auto_ptroder einer der intelligenten Boost-Zeiger scheint alle meine Bedürfnisse abzudecken.

std::auto_ptr<T>::

Einzelne Person besitzt das Objekt. Eigentumsübergang ist zulässig.

Verwendung: Hiermit können Sie Schnittstellen definieren, die die explizite Eigentumsübertragung anzeigen.

boost::scoped_ptr<T>

Einzelne Person besitzt das Objekt. Eigentumsübergang ist NICHT erlaubt.

Verwendung: Wird verwendet, um explizites Eigentum anzuzeigen. Das Objekt wird vom Destruktor zerstört oder beim expliziten Zurücksetzen.

boost::shared_ptr<T>( std::tr1::shared_ptr<T>)

Mehrfachbesitz. Dies ist ein einfacher Zeiger mit Referenzzählung. Wenn der Referenzzähler Null erreicht, wird das Objekt zerstört.

Verwendung: Wenn ein Objekt mehrere Owers mit einer Lebensdauer haben kann, die zur Kompilierungszeit nicht bestimmt werden kann.

boost::weak_ptr<T>::

Wird shared_ptr<T>in Situationen verwendet, in denen ein Zeigerzyklus auftreten kann.

Verwendung: Wird verwendet, um zu verhindern, dass Zyklen Objekte behalten, wenn nur der Zyklus eine gemeinsame Nachzählung verwaltet.


14
?? Was war die Frage?
Pacerier

9
Ich wollte nur darauf hinweisen, dass auto_ptr seit dem Posten dieser Frage zugunsten von (dem jetzt standardisierten) unique_ptr
Juan Campa

In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good (IMO) Kann das umformuliert werden? Ich verstehe es überhaupt nicht.
lolololol ol

@lololololol Du hast den Satz halbiert. In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good C++ program it is very rare to see RAW pointers passed around. RAW-Zeiger haben keine Besitzersemantik. Wenn Sie den Eigentümer nicht kennen, wissen Sie nicht, wer für das Löschen des Objekts verantwortlich ist. Es gibt mehrere Standardklassen, die zum Umschließen von Zeigern (std :: shared_ptr, std :: unique_ptr usw.) verwendet werden, die den Eigentümer und damit definieren Definieren Sie, wer für das Löschen des Zeigers verantwortlich ist.
Martin York

1
In C ++ 11 + VERWENDEN SIE NICHT auto_ptr! Verwenden Sie stattdessen unique_ptr!
Val sagt Reinstate Monica

Antworten:


20

Für mich decken diese 3 Arten die meisten meiner Bedürfnisse ab:

shared_ptr - Referenzzählung, Freigabe, wenn der Zähler Null erreicht

weak_ptr- wie oben, aber es ist ein "Sklave" für einen shared_ptr, kann nicht freigeben

auto_ptr- wenn die Erstellung und Freigabe innerhalb derselben Funktion erfolgt oder wenn das Objekt immer nur als ein Eigentümer betrachtet werden muss. Wenn Sie einen Zeiger einem anderen zuweisen, "stiehlt" der zweite das Objekt vom ersten.

Ich habe meine eigene Implementierung für diese, aber sie sind auch in verfügbar Boost.

Ich übergebe Objekte constimmer noch als Referenz ( wann immer möglich). In diesem Fall muss die aufgerufene Methode davon ausgehen, dass das Objekt nur während des Aufrufs aktiv ist.

Es gibt eine andere Art von Zeiger, die ich benutze und die ich hub_ptr nenne . Dies ist der Fall, wenn Sie über ein Objekt verfügen, auf das über darin verschachtelte Objekte zugegriffen werden muss (normalerweise als virtuelle Basisklasse). Dies könnte gelöst werden, indem ein a weak_ptran sie weitergegeben wird, aber es hat kein a shared_ptrfür sich. Da diese Objekte nicht länger als er leben würden, wird ihnen ein hub_ptr übergeben (es ist nur ein Template-Wrapper an einen regulären Zeiger).


2
Anstatt Ihre eigene Zeigerklasse (hub_ptr) zu erstellen, übergeben Sie diese * einfach an diese Objekte und lassen sie als Referenz speichern. Da Sie sogar anerkennen, dass die Objekte gleichzeitig mit der besitzenden Klasse zerstört werden, verstehe ich den Punkt, durch so viele Reifen zu springen, nicht.
Michel

4
Es ist im Grunde ein Designvertrag, um die Dinge klar zu machen. Wenn das untergeordnete Objekt hub_ptr empfängt, weiß es, dass das spitze Objekt zu Lebzeiten des Kindes nicht zerstört wird und kein Eigentum daran hat. Sowohl das enthaltene als auch das Containerobjekt stimmen einem klaren Regelwerk zu. Wenn Sie einen nackten Zeiger verwenden, können die Regeln dokumentiert werden, werden jedoch vom Compiler und vom Code nicht erzwungen.
Fabio Ceconello

1
Beachten Sie auch, dass Sie #ifdefs verwenden können, damit hub_ptr in Release-Builds auf einen nackten Zeiger typisiert wird, sodass der Overhead nur im Debug-Build vorhanden ist.
Fabio Ceconello

3
Beachten Sie, dass die Boost-Dokumentation Ihrer Beschreibung von scoped_ptr widerspricht. Es heißt, dass es ist noncopyableund dass das Eigentum nicht übertragen werden kann.
Alec Thomas

3
@Alec Thomas, du hast recht. Ich habe über auto_ptr nachgedacht und scoped_ptr geschrieben. Korrigiert.
Fabio Ceconello

23

Einfaches C ++ - Modell

In den meisten Modulen, die ich gesehen habe, wurde standardmäßig angenommen, dass das Empfangen von Zeigern keinen Besitz erhält. Tatsächlich waren Funktionen / Methoden, die den Besitz eines Zeigers aufgaben, sehr selten und drückten diese Tatsache in ihrer Dokumentation ausdrücklich aus.

Bei diesem Modell wird davon ausgegangen, dass der Benutzer nur Eigentümer dessen ist, was er explizit zuweist . Alles andere wird automatisch entsorgt (beim Verlassen des Bereichs oder über RAII). Dies ist ein C-ähnliches Modell, das durch die Tatsache erweitert wird, dass die meisten Zeiger Eigentum von Objekten sind, die sie automatisch oder bei Bedarf freigeben (meistens bei der Zerstörung dieser Objekte), und dass die Lebensdauer von Objekten vorhersehbar ist (RAII ist Ihr Freund, nochmal).

In diesem Modell sind Rohzeiger frei zirkulierend und meist nicht gefährlich (aber wenn der Entwickler klug genug ist, verwendet er stattdessen Referenzen, wann immer dies möglich ist).

  • rohe Zeiger
  • std :: auto_ptr
  • boost :: scoped_ptr

Smart Pointed C ++ - Modell

In einem Code voller intelligenter Zeiger kann der Benutzer hoffen, die Lebensdauer von Objekten zu ignorieren. Der Besitzer ist niemals der Benutzercode: Es ist der Smart Pointer selbst (wieder RAII). Das Problem ist, dass Zirkelreferenzen, die mit intelligenten Zeigern mit Referenzzählung gemischt werden, tödlich sein können. Sie müssen sich also sowohl mit gemeinsam genutzten als auch mit schwachen Zeigern befassen. Sie müssen also noch das Eigentum berücksichtigen (der schwache Zeiger könnte durchaus auf nichts verweisen, selbst wenn sein Vorteil gegenüber dem rohen Zeiger darin besteht, dass er es Ihnen sagen kann).

  • boost :: shared_ptr
  • boost :: schwach_ptr

Fazit

Unabhängig von den Modellen, die ich beschreibe, erhält der Empfang eines Zeigers , außer in Ausnahmefällen, nicht sein Eigentum, und es ist immer noch sehr wichtig zu wissen, wem wer gehört . Selbst für C ++ - Code werden häufig Referenzen und / oder intelligente Zeiger verwendet.


10

Ich habe kein gemeinsames Eigentum. Wenn Sie dies tun, stellen Sie sicher, dass es sich nur um Code handelt, den Sie nicht kontrollieren.

Das löst 100% der Probleme, da Sie verstehen müssen, wie alles zusammenwirkt.


2
  • Geteilter Besitz
  • boost :: shared_ptr

Wenn eine Ressource von mehreren Objekten gemeinsam genutzt wird. Der Boost shared_ptr verwendet die Referenzzählung, um sicherzustellen, dass die Zuweisung der Ressource aufgehoben wird, wenn alle fertig sind.


2

std::tr1::shared_ptr<Blah> ist ziemlich oft die beste Wahl.


2
shared_ptr ist am häufigsten. Aber es gibt noch viel mehr. Jeder hat sein eigenes Nutzungsmuster und gute und schlechte Klagegründe. Ein bisschen mehr Beschreibung wäre schön.
Martin York

Wenn Sie mit einem älteren Compiler nicht weiterkommen, basiert auf boost :: shared_ptr <blah> std :: tr1 :: shared_ptr <blah>. Es ist eine Klasse, die so einfach ist, dass Sie sie wahrscheinlich aus Boost rippen und verwenden können, selbst wenn Ihr Compiler von der neuesten Version von Boost nicht unterstützt wird.
Branan

2

Ab Boost gibt es auch die Zeigercontainerbibliothek . Diese sind etwas effizienter und benutzerfreundlicher als ein Standardcontainer mit intelligenten Zeigern, wenn Sie die Objekte nur im Kontext ihres Containers verwenden.

Unter Windows gibt es die COM-Zeiger (IUnknown, IDispatch und Freunde) und verschiedene intelligente Zeiger für deren Verarbeitung (z. B. CComPtr der ATL und die intelligenten Zeiger, die von der Anweisung "import" in Visual Studio basierend auf der Klasse _com_ptr automatisch generiert werden) ).


1
  • Ein Eigentümer
  • boost :: scoped_ptr

Wenn Sie Speicher dynamisch zuweisen müssen, aber sicher sein möchten, dass er an jedem Exit-Punkt des Blocks freigegeben wird.

Ich finde das nützlich, da es leicht wieder eingesetzt und freigegeben werden kann, ohne sich jemals um ein Leck sorgen zu müssen


1

Ich glaube nicht, dass ich jemals in der Lage war, an meinem Design mitzuwirken. Tatsächlich ist der einzige gültige Fall, den ich mir vorstellen kann, das Fliegengewichtsmuster.


1

yasper :: ptr ist eine leichte, boost :: shared_ptr-ähnliche Alternative. Es funktioniert gut in meinem (vorerst) kleinen Projekt.

Auf der Webseite unter http://yasper.sourceforge.net/ wird Folgendes beschrieben:

Warum einen anderen C ++ Smart Pointer schreiben? Es gibt bereits mehrere hochwertige Smart-Pointer-Implementierungen für C ++, insbesondere das Boost-Pointer-Pantheon und Lokis SmartPtr. Für einen guten Vergleich der Smart-Pointer-Implementierungen und wenn ihre Verwendung angemessen ist, lesen Sie bitte Herb Sutters The New C ++: Smart (er) Pointers. Im Gegensatz zu den umfangreichen Funktionen anderer Bibliotheken ist Yasper ein eng fokussierter Referenzzählzeiger. Es entspricht genau den Richtlinien shared_ptr von Boost und RefCounted / AllowConversion von Loki. Mit Yasper können C ++ - Programmierer die Speicherverwaltung vergessen, ohne die großen Abhängigkeiten des Boost einzuführen oder sich mit den komplizierten Richtlinienvorlagen von Loki vertraut zu machen. Philosophie

* small (contained in single header)
* simple (nothing fancy in the code, easy to understand)
* maximum compatibility (drop in replacement for dumb pointers)

Der letzte Punkt kann gefährlich sein, da yasper riskante (und dennoch nützliche) Aktionen zulässt (z. B. Zuweisung zu Rohzeigern und manuelle Freigabe), die von anderen Implementierungen nicht zugelassen werden. Seien Sie vorsichtig, verwenden Sie diese Funktionen nur, wenn Sie wissen, was Sie tun!


1

Es gibt eine andere häufig verwendete Form des einzelnen übertragbaren Eigentümers, und dies ist vorzuziehen, auto_ptrda dadurch die Probleme vermieden werden, die durch auto_ptrdie verrückte Beschädigung der Zuweisungssemantik verursacht werden .

Ich spreche von niemand anderem als swap. Jeder Typ mit einer geeigneten swapFunktion kann als intelligenter Verweis auf einen Inhalt verstanden werden, den er besitzt, bis der Besitz durch Austauschen auf eine andere Instanz desselben Typs übertragen wird. Jede Instanz behält ihre Identität, wird jedoch an neuen Inhalt gebunden. Es ist wie eine sicher wiederbindbare Referenz.

(Es ist eher eine intelligente Referenz als ein intelligenter Zeiger, da Sie ihn nicht explizit dereferenzieren müssen, um an den Inhalt zu gelangen.)

Dies bedeutet, dass auto_ptr weniger notwendig wird - es wird nur benötigt, um die Lücken zu füllen, in denen Typen keine gute swapFunktion haben. Aber alle Standardcontainer tun es.


Vielleicht wird es weniger notwendig (ich würde sagen, scoped_ptr macht es weniger notwendig als dies), aber es geht nicht weg. Eine Swap-Funktion hilft Ihnen überhaupt nicht, wenn Sie etwas auf dem Heap zuweisen und jemand wirft, bevor Sie es löschen, oder wenn Sie es einfach vergessen.
Michel

Genau das habe ich im letzten Absatz gesagt.
Daniel Earwicker

0
  • Ein Besitzer: Aka auf Kopie löschen
  • std :: auto_ptr

Wenn der Ersteller des Objekts das Eigentum explizit an eine andere Person übergeben möchte. Dies ist auch eine Möglichkeit, den Code zu dokumentieren, den ich Ihnen gebe, und ich verfolge ihn nicht mehr. Stellen Sie daher sicher, dass Sie ihn löschen, wenn Sie fertig sind.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.