Einige Fragen zu Smartptr
-
Ich bin sehr an schönem C++ interessiert und auch kein großer Java/C#-Fan.
Deswegen frage ich hier ja nach, wann ich einen normalen Zeigern und wann Smartptr verwenden soll. Viele hier, bei denen ich davon ausgehe, sie seien stilstarke Programmierer, nutzen doch Smartptr und boost bietet das ja vermutlich auch nicht zum Spaß an.Wann sollte ich denn jetzt was nutzen?
Und ich nutze nicht grundsätzlich Zeiger, wenn nicht nötig, aber einfaches Beispiel: Ich habe einen Renderer, bei welchem man 3D-Modelle zum Rendern "anmelden" kann. Diese geraten dadurch in den Renderzyklus. Wenn ein Objekt nicht mehr in dem Container des Renderers ist, ist es aber nicht erwünscht, dass es zerstört wird, also ist das nicht der typische RAII-Fall.
-
Ich würde sagen, das wichtigste ist, überhaupt pointers zu vermeiden. Wenn das nicht geht, sind smart pointers wohl sehr oft besser als rohe Zeiger. Außer manchmal, wenn wie bei ptrmap noch angemessener ist.
Und ich nutze nicht grundsätzlich Zeiger, wenn nicht nötig, aber einfaches Beispiel: Ich habe einen Renderer, bei welchem man 3D-Modelle zum Rendern "anmelden" kann. Diese geraten dadurch in den Renderzyklus. Wenn ein Objekt nicht mehr in dem Container des Renderers ist, ist es aber nicht erwünscht, dass es zerstört wird, also ist das nicht der typische RAII-Fall.
Das wäre wohl einer der Fälle, wo rohe Zeiger mal besser als smart pointer sind.
-
Eisflamme schrieb:
Wann sollte ich denn jetzt was nutzen?
Den Smart Pointer, den ich am meisten benutzt ist wohl
scoped_ptroder selbststd::auto_ptr. Den shared Pointer benutze ich so gut, wie gar nie. Die verwaltenden Container von boost hingegen benutze ich immer mal wieder gerne.
Normale Zeiger benutze ich überall dort, wo ich nichts mit Speicher zu tun habe, die nullbarkeit aber brauche (optionale Funktionalität z.B), ansonsten nehme ich Referenzen.Und ich nutze nicht grundsätzlich Zeiger, wenn nicht nötig, aber einfaches Beispiel: Ich habe einen Renderer, bei welchem man 3D-Modelle zum Rendern "anmelden" kann. Diese geraten dadurch in den Renderzyklus. Wenn ein Objekt nicht mehr in dem Container des Renderers ist, ist es aber nicht erwünscht, dass es zerstört wird, also ist das nicht der typische RAII-Fall.
Naja. Was bringt es dir, wenn du ein Objekt hast, welches nicht zerstört ist, aber auf das du nicht mehr zugreifen kannst? - Wenn es das wäre, dann hast du ja noch irgendwo einen Zeiger darauf und das Objekt wird nicht zerstört, wenn du es aus dem Container entfernst, sondern der Referenzzähler wird dann einfach um eins nach unten gehen.
//Edit
Obwohl ich eine Möglichkeit für den Renderer mit Smart Pointer genannt habe, unterstütze ich die Meinung von volkard hier keine Smart Pointer einzusetzen. Der Container hat ja nichts mit dem Speicher zu tun. Der Aufrufer muss einfach dafür sorgen, dass das Objekt genug lange lebt, dass es da keine Probleme gibt.
-
Wollte gerade nachfragen, wie ich das Problem denn mit smart_ptr überhaupt lösen könnte

Aber ok, hab den Punkt. Ich überdenke Mal meinen Aufbau nochmal.
-
Eisflamme schrieb:
Nutzt ihr Smartpointer anstatt von jedem Zeiger oder fallen euch Ausnahmen ein?
Nein, höchstens vor besitzenden Zeigern. Für passive Verweise reichen rohe Zeiger oder Referenzen meistens.
Eisflamme schrieb:
Ich bin nicht ganz sicher, wann ich weak_ptr und wann shared_ptr benutzen sollte. Was, wenn ich beispielsweise einer Funktion, die irgendwas macht, einen Zeiger übergeben möchte, der in der aufrufenden Funktion als shared_ptr bekannt ist?
Zwei Dinge sind mir bisher etwas negativ aufgefallen:
shared_ptrwird oft überbewertet, und deshalb auch oft aus Faulheit missbraucht. Wie volkard z.B. erwähnt hat, solltest du z.B. lieber Pointer-Container statt Container mitshared_ptrverwenden. Und für lokale RAII-Zeiger nimmst duscoped_ptr. Oder fürs Transferieren von Besitzauto_ptr. Oder für die Übergabe an Funktionen normale Zeiger oder Referenzen, wenn die Quelle gültig bleibt.shared_ptrbenötigst du nur, wenn du wirklich geteilte Besitzverhältnisse hast. Undweak_ptr, wenn du Zeiger haben willst, die das Objekt nicht am Leben erhalten.- Es gibt ein paar bekannte Smart-Pointer (siehe Punkt 1), doch keiner davon hat Kopiersemantik. Merkwürdigerweise scheinen das auch nur wenige Leute zu benötigen, oder die meisten stricken es von Hand nach. Ein Einsatzbereich ist zum Beispiel das Pimpl-Idiom, doch auf der Boost-Seite empfehlen sie dafür tatsächlich
shared_ptr.
-
Eisflamme schrieb:
Nutzt ihr Smartpointer anstatt von jedem Zeiger oder fallen euch Ausnahmen ein?
Ja, bei besitzenden Zeigern, es sei den ich verwende eine Datenstruktur die Zeiger entsprechend behandelt (wie z.B. die Pointercontainer von Boost). Wobei ich nach Möglichkeit die "billigen" Smartpointer wie boost::scoped_ptr verwende, und die "teuren" wie shared_ptr/weak_ptr nur wenn nötig.
Eisflamme schrieb:
Wie nennt ihr eure typedefs, wenn ihr shared_- und weak_ptr nutzt?

Ich benutze kaum typedefs, es sei den die Konstrukte werden wirklich lang (und für ein boost::scoped_ptr<Typ> würde ich noch kein typedef verwenden).
-
Noch zwei Fragen:
Anderer Fall ist, wenn ich Factory-Zeug nutze (siehe anderer Thread mit "dickem ctor" und so), dann erhalte ich zwangsläufig ja auch einen Zeiger, den ich irgendwo speichern muss.
Dann wäre doch ein scoped_ptr ganz nett. Aber da der normalerweise direkt initialisiert werden muss, was mit meiner Factory nicht geht, würde das dann ja quasi so ablaufen:
class Model; class Application{ private: scoped_ptr<Model> model_; public: Application(); }; // in Application.cpp: Application::Application() { model_.reset(Factory.CreateModel()); }Jetzt wirkt es für mich irgendwie etwas unnatürlich, dieses reset. Kann auch Blödsinn sein, aber so würde ich das dann doch machen. Ist das in euren Augen jetzt schlecht, das so zu lösen? Was spricht gegen den scoped_ptr an dieser Stelle und dafür, das model_ selbst im dtor zu deleten?
Ne ganz andere Frage, die jetzt so an das geht, was volkard von wegen Zeiger vermeiden sagt:
Was mache ich denn bei Containern? push_back nutzt bei nem vector ja z.B. einen copy-ctor, oder? Was, wenn ich den für mein Objekt eigentlich sperren lassen möchte, weil er keinen Sinn ergibt?
Oder ich möchte ein performantes Einfügen in den vector gestatten. Wenn ich bei meinem Model die ganzen Modelldaten kopiere, ist das ein extremer Overhead, den ich bei der Arbeit mit Zeigern nicht hätte.Auch wenn ich jetzt wahrscheinlich wieder C++ im Ganzen missverstehe, freue ich mich auf eure Hilfe.

-
Eisflamme schrieb:
Ne ganz andere Frage, die jetzt so an das geht, was volkard von wegen Zeiger vermeiden sagt:
Was mache ich denn bei Containern? push_back nutzt bei nem vector ja z.B. einen copy-ctor, oder? Was, wenn ich den für mein Objekt eigentlich sperren lassen möchte, weil er keinen Sinn ergibt?Heute Zeiger, morgen zunehmend emplace_back. Auf keinen Fall unnötige Standardkonstruktoren oder gar Kopierkonstruktoren machen; dann täten Zeiger weniger weh.
Oder ich möchte ein performantes Einfügen in den vector gestatten. Wenn ich bei meinem Model die ganzen Modelldaten kopiere, ist das ein extremer Overhead, den ich bei der Arbeit mit Zeigern nicht hätte.
Extremes Kopieren von Vector? Naja, manchmal zwei Kopierungen pro Einfügung. Aber vielleicht ruckelig. Große Objekte vielleicht lieber mit verketteten Listen verwalten.
Aber Modelle riechen für mich recht stark nach Zeigern.
-
In der ganzen 3D-Umgebung habe ich halt viel mit so dicken Datenobjekten zu tun. Da gibt es z.B. auch Texturen oder intensive Materialsachen.
Das würdest Du alles mit rohen Zeigern basteln? Oder bieten sich bei Maps dann so diese boost::ptr_map an oder was das ist (ich schaue mir das auch nochmal an demnächst)?
-
Wenn du zum Beispiel die Ressourcen zu Beginn des Programms durch einen Ressourcen Manager laden lässt und dieser am Ende des Programmes die Ressourcen wieder freigibt, wieso sollte man da nicht normale Zeiger verwenden?
Grüssli
-
Dann kapier ich nicht, was scoped_ptr und shared_ptr überhaupt sollen.
Wenn das irgendwo steht und ich's überlesen hab, reicht mir auch der Hinweis, so ist's nicht!In der Boost-Lib. steht, dass Smartptr fast wie normale Pointer sind, nur automatisch den Speicher freigeben. Also hätte ich die jetzt genommen, wenn es umständlich ist (wie bei ner map, wo man alle Elemente durchgehen muss, um alles zu löschen, na ja).
shared_ptr nutzt jetzt aus, wenn der Zeiger von verschiedenen Leuten genutzt wird ohne dass Verantwortlichkeiten klar sind, das kann in Einzelfällen Mal sein, gut. Aber scoped_ptr dürfte ich dann ja nach deiner Logik nicht verwenden, denn der wird am Ende seines Blocks zerstört. Wenn Block == Klasse, ist der dtor verantwortlich. Wenn Block < Klasse, greift ja wieder die Aussage von irgendwo hier im Thread, dass man die Übersichtlichkeit hat und es daher doch selbst machen kann.
-
Eisflamme schrieb:
Dann kapier ich nicht, was scoped_ptr und shared_ptr überhaupt sollen.
scoped_ptr ist immer dann gut, wenn man lokal Objekte auf den Heap erzeugen muss (z.B. weil eine Bibliothek nicht mit Stackvariablen klar kommt). Dieser Smartpointer ist bei mir auch der mit Abstand am häufigsten verwendete. shared_ptr/weak_ptr ist genau dann sinnvoll, wenn ein Objekt a) von vielen Anderen gleichzeitig verwendet werden muss, und b) keine zentrale Zerstörung möglich ist.
Sobald die Objekthaltung aber an einer Stelle erfolgen kann (was ich eher für die Regel halte), sollte man shared_ptr nicht verwenden.
Eisflamme schrieb:
In der Boost-Lib. steht, dass Smartptr fast wie normale Pointer sind, nur automatisch den Speicher freigeben.
Im Grundsatz stimmt dies auch, nur kann, je nach Smartpointer, der Verwaltungsaufwand recht hoch sein. Sofern man entweder auf Zeiger verzichten kann, oder wenn es möglich ist, die Erzeugung und Zerstörung an einer Stelle halten, sollte man sich eher auf Objekte, ptr_container oder billige Smartpointer beschränken.
Eisflamme schrieb:
Also hätte ich die jetzt genommen, wenn es umständlich ist (wie bei ner map, wo man alle Elemente durchgehen muss, um alles zu löschen, na ja).
Die prt_container machen eben dies von Haus aus, ohne eine Referenzzählung durchzuführen.
-
Ich hoffe ich hijacke jetzt nicht den Thread, aber ich hab ne Frage dazu. Mir sind shared_ptr etc relativ unbekannt und ich brauch mal nen mehr praktisches Beispiel in einfacheren Worten.
Folgende Situation:Ich verwende in einem Spiel relativ viele Listen mit Zeigern auf Objekte. Die jeweiligen Klassen übernehmen den Besitz dieser Objekte wie Quads etc. Demnach muss ich die Listen auch jedesmal im Destruktor durchlaufen und die Objekte mit delete löschen, damit es keine Lecks im Speicher gibt. Allerdings sollen andere Klassen oder die Hauptroutine von diesen Objekten jeweils Zeiger erhalten bei Events wie Kollissionen etc.
Mir gehts hauptsächlich darum, dass am Ende des Programnmes oder wenn die Klasse die die Liste führt zerstört wird, der std::vector oder std::list aufgeräumt wird ohne dass ich das manuell machen muss (bzw die Objekte die darin leben).
Welcher Container wäre hier der richtige?
std::auto_ptr()?rya.
-
Ok, das meiste habe ich jetzt verstanden, aber zum scoped_ptr bin ich immer noch nicht sicher:
scoped_ptr ist immer dann gut, wenn man lokal Objekte auf den Heap erzeugen muss (z.B. weil eine Bibliothek nicht mit Stackvariablen klar kommt).
-
Lokal heisst innerhalb einer meiner Funktionen? Wo am Anfang das new steht und am Ende das delete? Das ist fuer mich per se aber dann auch kein Argument, es sei denn, man ist sich ueber das Exceptionverhalten der aufgerufenen Bibliotheksfunktion nicht sicher... aber das sollte man imo sowieso immer sein, oder?
-
Das mit den geteilten Verantwortlichkeiten verstehe ich eigentlich auch nicht mehr so ganz. Ich wuerde fast immer dafuer sorgen, dass irgend ein Objekt eben die Hauptverantwortlichkeit traegt, sonst fliegt ein Zeiger ja irgendwie random im Raum rum. Hat jemand dafuer ein Beispiel?
-
Ist es dann ratsam immer die ptr-Container zu nehmen, wenn ich einen Container mit Objektzeigern (wie bei meinem Model) nutze? Oder gibt es auch hier nicht auf den ersten Blick ersichtliche Nachteile?
Snorcher24:
auto_ptr ist ja kein Container. Und ohne deine Frage damit komplett zu beantworten, tut es diese Seite aber zumindest zum Grossteil: http://www.boost.org/doc/libs/1_44_0/libs/ptr_container/doc/ptr_container.html
Sieht nett aus.
-
-
Eisflamme schrieb:
Ok, das meiste habe ich jetzt verstanden, aber zum scoped_ptr bin ich immer noch nicht sicher:
scoped_ptr ist immer dann gut, wenn man lokal Objekte auf den Heap erzeugen muss (z.B. weil eine Bibliothek nicht mit Stackvariablen klar kommt).
-
Lokal heisst innerhalb einer meiner Funktionen? Wo am Anfang das new steht und am Ende das delete? Das ist fuer mich per se aber dann auch kein Argument, es sei denn, man ist sich ueber das Exceptionverhalten der aufgerufenen Bibliotheksfunktion nicht sicher... aber das sollte man imo sowieso immer sein, oder?
-
Das mit den geteilten Verantwortlichkeiten verstehe ich eigentlich auch nicht mehr so ganz. Ich wuerde fast immer dafuer sorgen, dass irgend ein Objekt eben die Hauptverantwortlichkeit traegt, sonst fliegt ein Zeiger ja irgendwie random im Raum rum. Hat jemand dafuer ein Beispiel?
-
Ist es dann ratsam immer die ptr-Container zu nehmen, wenn ich einen Container mit Objektzeigern (wie bei meinem Model) nutze? Oder gibt es auch hier nicht auf den ersten Blick ersichtliche Nachteile?
Snorcher24:
auto_ptr ist ja kein Container. Und ohne deine Frage damit komplett zu beantworten, tut es diese Seite aber zumindest zum Grossteil: http://www.boost.org/doc/libs/1_44_0/libs/ptr_container/doc/ptr_container.html
Sieht nett aus.Jau, hab mich für boost::ptr_vector entschieden

Danke trotzdem
rya.
-
-
Eisflamme schrieb:
Nutzt ihr Smartpointer anstatt von jedem Zeiger oder fallen euch Ausnahmen ein?
Ich nutze fast ausschliesslich Smart-Pointer. Wobei Smart-Pointer jetzt nicht automatisch shared_ptr bedeutet. scoped_ptr ist auch ein Smart-Pointer, sogar der konzeptionell fragwürdige auto_ptr ist ein Smart-Pointer. Interessant dass die Anti-shared_ptr Fraktion das wiedermal total ignoriert hat

Ausnahmen gibt es aber natürlich. z.B. wenn man Zeiger als schnelle Iteratoren über Arrays verwendet. Als Beispiel fallen mir ein: Software-Rendering, Low-Level String-Manipulation, Parser etc.
Wo ich kann versuche ich allerdings ganz auf Zeiger zu verzichten, seien sie nun "smart" oder nicht. z.B. indem ich Referenzen statt Zeigern übergebe.
Ich bin nicht ganz sicher, wann ich weak_ptr und wann shared_ptr benutzen sollte.
weak_ptr verwende ich fast nur um Zyklen aufzubrechen. Ganz ganz selten mal ohne dass es zu Zyklen kommen könnte, wenn z.B. ein Objekt ein anderes kennen soll, aber dessen Zerstörung nicht verhindern (weil das Objekt z.B. viel Resourcen verschlingt). Solche Fälle sind allerdings selten, und lassen sich dann auch oft eleganter lösen.
Was, wenn ich beispielsweise einer Funktion, die irgendwas macht, einen Zeiger übergeben möchte, der in der aufrufenden Funktion als shared_ptr bekannt ist?
Kommt drauf an.
Wenn die Funktion nicht "shared ownership" übernehmen muss, dann musst du keinen shared_ptr übergeben, es reicht eine Referenz.
Wenn die Funktion "shared ownership" übernehmen darf, dann übergibt einen "shared_ptr const&". weak_ptr macht IMO gar keinen Sinn.shared_ptr hat ja anscheinend schon ein bisschen zu tun, zählt intern Referenzen mit etc. Wenn ich da bei jedem Funktionsaufruf eine Instanz erstellen und danach zerstören lassen würde, wäre das evtl. Overhead, daher macht da für mich der weak_ptr mehr Sinn, oder?
weak_ptr hat gleich viel oder mehr Overhead. Du musst ja den weak_ptr erstmal locken bevor du ihn verwenden kannst. Dabei entsteht ein shared_ptr. Der muss auch irgendwie konstruiert und wieder zerstört werden.
Entweder du übergibst wie schon erwähnt eine Referenz, oder eine (const) Referenz auf den shared_ptr, oder ggf. noch einen rohen Pointer (macht IMO nur Sinn wenn der Zeiger null sein darf).BTW: der Referenz-Zähler wird - sofern die Boost mit Threading-Support konfiguriert ist - atomar inkrementiert und dekrementiert (-> Interlocked Instructions). 1x shared_ptr kopieren und gleich wieder zerstören kann da gleich mal 100+ Zyklen brauchen, auch wenn alles Inline erweitert wird. Und die meisten Compiler können Interlocked-Aufrufe auch nicht wegoptimieren, was die Sache doppelt schlimm macht

Wie nennt ihr eure typedefs, wenn ihr shared_- und weak_ptr nutzt?

Ich verwende meist gar keine typedefs.
Dort wo sie in unseren Projekten verwendet werden, heissen sietypederf boost::shared_ptr<Type> TypePtr;
-
Eisflamme schrieb:
- Lokal heisst innerhalb einer meiner Funktionen? Wo am Anfang das new steht und am Ende das delete? Das ist fuer mich per se aber dann auch kein Argument, es sei denn, man ist sich ueber das Exceptionverhalten der aufgerufenen Bibliotheksfunktion nicht sicher... aber das sollte man imo sowieso immer sein, oder?
Grundsätzlich gibt es zum scoped_ptr mehr als ein Argument. Das Exceptionverhalten ist eines davon, das andere ist das man unproblematisch ein return in eine Funktion bauen kann. Ich bin ein Favorit davon Funktionen so früh als möglich zu beenden (ist mir lieber als verschachtelte if-Konstrukte).
Eisflamme schrieb:
- Das mit den geteilten Verantwortlichkeiten verstehe ich eigentlich auch nicht mehr so ganz. Ich wuerde fast immer dafuer sorgen, dass irgend ein Objekt eben die Hauptverantwortlichkeit traegt, sonst fliegt ein Zeiger ja irgendwie random im Raum rum. Hat jemand dafuer ein Beispiel?
Wenn du genau das hast: Ein Objekt trägt die Hauptverantwortung, dann sind shared_ptr fehl am Platz und ptr-Container besser.
Ein Beispiel für den Einsatz von einem shared_ptr könnte z.B. sein, wenn zwei nicht modale Masken auf gleiche Daten zugreifen sollen, aber nicht klar ist, welche zuerst geschlossen wird. Wenn beide die Daten per shared_ptr halten, ist sichergestellt, das die Daten erst mit der letzten gelöscht werden.
Eisflamme schrieb:
- Ist es dann ratsam immer die ptr-Container zu nehmen, wenn ich einen Container mit Objektzeigern (wie bei meinem Model) nutze? Oder gibt es auch hier nicht auf den ersten Blick ersichtliche Nachteile?
Ich weiß nicht ob das inzwischen geändert wurde (wir haben hier eine ältere Boost-Bibliothek im Einsatz), aber die Schnittstellen der ptr-Container haben sich imho nicht mit allen STL-Konstrukten vertragen (was aber nur selten bei mir zum tragen kam).