smartpointer (auto_ptr) Vs normaler Pointer
-
Btw, dein erstes Beispiel klappt nicht so ganz - die Funktion bekommt eine Kopie von ptr, mit der sie arbeitet; die Variable des Hauptprogramms wird nicht beeinflusst.
was wurde hier kopiert? eigentlich die Adresse, die ptr enthält.
void CallInputSPoint(int *inputPtr) { //dosomething with inputPtr; } void main() { int* ptr = new int[10]; CallInputSPoint(ptr); delete ptr; ptr = NULL; }hier ptr hat INHALT 0x1000, wo int[10] gespeichert sind.(er selber hat Adresse 0x1200), jetzt wird ptr kopiert zu inputPtr, d.h. 0x1000 wird kopiert. inputPtr hat INHALT 0x1000, (er selber hat Adress 0x1204), was man in der Unterfunction mit Adresse 0x1000 macht, wirkt auch main Programm.
-
Ja, so könnte das sogar funktionieren (wenn du jetzt noch "delete**[]** ptr;" schreibst) - die Funktion kann den Inhalt hinter ptr verändern, aber sie kann nicht den Zeiger selber umbiegen (und in deinem obigen Beispiel hattest du einen NULL-Pointer übergeben).
-
es gibt doch keinen Unterschied,wenn ich ein Ptr NULL zuweise oder mit einer "echten Adresse"
-
Ein weiterer Vorteil (1):
Ein guter Smart-Pointer ist immer initialisiert. d.h. man kann sich die die Zuweisung
int* ptr = NULL;sparen.
Damit ergibt sich Vorteil (2):
Der Code kann kompakter geschrieben werden, da man die Initialisierung und freigabe weglassen kann. Damit können einige Destruktoren und manchmal auch ein Konstruktor wegfallen.
Damit ist der Code machmal einfacher zu lesen.
Ein weiter vorteil (3):
Man spart Verwaltungsaufwang: Wenn der dynamischer Speicher an einer Stelle angefordert und an einer anderen Stelle freigegeben wird oder an verschieden Stellen benötigt wird, kann ein guter Smart-Pointer den ganzen Verwaltungskram unnötig machen.
Wenn ein Anwendung komplett auf Smart-Pointer umgestellt ist hat man eigendlich keine Probleme mehr mit Speicherlöcher.
Gute Smart-Pointer:
z.B. die Smart-Pointer aus der Boost.Es gibt selbstverständlich noch viele weitere Smart-Pointer. Da der shared_ptr 1:1 aus der Boost in den TR1 auf genommen wurde, kann ich z.Z. nur boost::shared_ptr empfehlen.
Sven
-
Smart Pointer bringen zwei primäre Vorteile mit, die dir gerade in größeren Projekten das Leben massiv vereinfachen werden (mein Wort drauf!). Gut, auto_ptr ist nicht gerade ein Beispiel für einen besonders tollen Smart Pointer, besser sind da schon scoped_ptr und shared_ptr von boost.
Vorteil 1: Ressourcenlecks vermeiden.
Oben in deinem Minibeispiel mag das ja noch nicht so kompliziert aussehen, das delete nicht zu vergessen. Aber stell dir mal eine Funktion vor, die ein paar mehr Sachen zu tun hat und die dann am Ende als Aufräumarbeit die Ressource wieder freigeben muss. Das Problem ist dann, dass so eine Funktion in der Regel verschiedene Möglichkeiten hat, verlassen zu werden. Verschiedene return points abhängig von anderen Bedingungen, dazu kommen dann noch Exceptions. Du musst sicherstellen, dass die Ressource an jedem exit point ordnungsgemäß freigegeben wird! Irgendwann wirst du es irgendwo vergessen, mein Wort drauf...
Mit einem Smart Pointer gibt es das Problem nicht. Wenn die Funktion verlassen wird und der Smart Pointer dabei als lokale Variable automatisch zerstört wird, gibt er die Ressource frei - egal, ob die Funktion durch return oder durch eine Exception verlassen wird.Vorteil 2: Speicherzugriffsfehler vermeiden.
Der Vorteil folgt eigentlich direkt aus dem ersten. In einem größeren Projekt wirst du früher oder später Ressourcen über verschiedene Teile deines Programms teilen, dann haben unterschiedliche Bereiche möglicherweise einen Pointer auf denselben Speicherbereich, mit dem sie jeweils auch arbeiten müssen. Frage: Wer ist dafür verantwortlich, die Ressource freizugeben, und wann?
Es mag vielleicht in der Theorie als kein so großes Problem erscheinen, mit sorgfältiger Planung diese Verantwortlichkeiten festzulegen, aber ich würde mal behaupten, früher oder später vergisst man doch, alle Pointer auf die (dann gelöschte) Speicherstelle zu entfernen. Und wenn dieser Pointer das nächste Mal verwendet wird - Segmentation fault. Viel Spaß bei der Fehlersuche...
Mit einem shared_ptr wäre das nicht passiert. Solange nicht der letzte von denen, die auf dieselbe Ressource verweisen, gelöscht wird, wird die Ressource nicht freigegeben, und demzufolge kann auch im Normalfall kein Zugriffsfehler (vor allem kein sporadischer!) passieren. Man hätte dann in diesem Beispiel zwar wieder ein Speicherleck, aber eines, das im Vergleich leicht zu finden ist, weil die Ressource ja (fehlerhafterweise) noch benutzt wird...
-
netrobot schrieb:
es gibt doch keinen Unterschied,wenn ich ein Ptr NULL zuweise oder mit einer "echten Adresse"
Doch, gibt es - versuch doch mal das folgende Programm zu starten:
int main() { int* ptr=NULL; *ptr=4711; return 0; }
-
sven_jo schrieb:
Ein weiterer Vorteil (1):
Ein guter Smart-Pointer ist immer initialisiert. d.h. man kann sich die die Zuweisung
int* ptr = NULL;sparen.
Damit ergibt sich Vorteil (2):
Der Code kann kompakter geschrieben werden, da man die Initialisierung und freigabe weglassen kann. Damit können einige Destruktoren und manchmal auch ein Konstruktor wegfallen.
Damit ist der Code machmal einfacher zu lesen.
Ein weiter vorteil (3):
Man spart Verwaltungsaufwang: Wenn der dynamischer Speicher an einer Stelle angefordert und an einer anderen Stelle freigegeben wird oder an verschieden Stellen benötigt wird, kann ein guter Smart-Pointer den ganzen Verwaltungskram unnötig machen.
Wenn ein Anwendung komplett auf Smart-Pointer umgestellt ist hat man eigendlich keine Probleme mehr mit Speicherlöcher.
Gute Smart-Pointer:
z.B. die Smart-Pointer aus der Boost.Es gibt selbstverständlich noch viele weitere Smart-Pointer. Da der shared_ptr 1:1 aus der Boost in den TR1 auf genommen wurde, kann ich z.Z. nur boost::shared_ptr empfehlen.
Sven
Nachteil: ptr wird zur Laufzeit festgestellt, d.h. es daurt länger als normaler pointer
-
Smart-Pointer wie die aus boost sind im Allgemeinen ein sehr dünnes Gefüge um die normalen Pointer drum. Ich wäre gespannt auf ein (sinnvolles) Programmbeispiel, bei dem man Unterschiede in der Zugriffszeit und Nutzung tatsächlich MESSEN könnte. Ein guter Compiler dürfte fast alles beim Zugriff wegoptimieren.
Das einzige, das geschwindigkeitsmäßig relevant werden kann, wäre die Referenzzählverwaltung der shared_ptr, aber selbst das sollte durch die Vorteile für 98% der Fälle mehr als aufgewogen sein
Ein echter Nachteil im Alltag sieht jedenfalls anders aus.Ach ja, hier finden sich sogar ein paar Vergleichswerte: http://www.boost.org/libs/smart_ptr/smarttests.htm
-
sven_jo schrieb:
Ein weiterer Vorteil (1):
Ein guter Smart-Pointer ist immer initialisiert. d.h. man kann sich die die Zuweisungint* ptr = NULL;sparen.
das ist ja zum heulen. ich schreibe NIE int* ptr = NULL; und das hat auch gute gründe.
Damit ergibt sich Vorteil (2):
Der Code kann kompakter geschrieben werden, da man die Initialisierung und freigabe weglassen kann. Damit können einige Destruktoren und manchmal auch ein Konstruktor wegfallen.eine klasse soll einen zweck erfüllen. das ist sicher nicht der fall, wenn du solche probleme hast.
Ein weiter vorteil (3):
Man spart Verwaltungsaufwang: Wenn der dynamischer Speicher an einer Stelle angefordert und an einer anderen Stelle freigegeben wird oder an verschieden Stellen benötigt wird, kann ein guter Smart-Pointer den ganzen Verwaltungskram unnötig machen.das ist in wirklichkeit dein angeblicher vorteil(2). und mit dem selben fehler behaftet.
Wenn ein Anwendung komplett auf Smart-Pointer umgestellt ist hat man eigendlich keine Probleme mehr mit Speicherlöcher.
und hier liegt der hund begraben. wer ohne smartpointer speicherlöcher schreibt, der sollte sie auch ohne smartponter erstmal wegmachen. wenn er dann programmieren kann, sollte er nochmal über die sache nachdenken. es gibt in c++ mit RAII und exceptions kein speicherlochproblem. deswegen brauchen wir auch keine javaistischen techniken, sie wegzumachen.
-
volkard schrieb:
int* ptr = NULL;ich schreibe NIE int* ptr = NULL; und das hat auch gute gründe.
super! so eine aussage und dann nennste die gründe net

also kannst du bitte mal einen deiner gründe nennen, damit andere auch etwas dazulernen.
-
icepacker schrieb:
also kannst du bitte mal einen deiner gründe nennen, damit andere auch etwas dazulernen.
, daß man variablen so spät wie möglich definieren und so lokal wie möglich halten sollte. entsprechend legt man diesen zeiger erst an, wenn man ihm einen *sinnvollen* wert geben kann.
-
int* CallInputPoint() { int * localPtr = NULL; return localPtr; } void main() { for(int i = 0; i<123;i++) { int* ptr = NULL; ptr = CallInputPoint(); // stell dir hier ein break vor! delete ptr; ptr = NULL; } }boost::shared_ptr<int> CallInputPoint() { boost::shared_ptr<int> localPtr; return localPtr; } void main() { for(int i = 0; i<123;i++) { boost::shared_ptr<int> ptr = CallInputPoint(); // stell dir hier ein break vor! } }
-
volkard schrieb:
Damit ergibt sich Vorteil (2):
Der Code kann kompakter geschrieben werden, da man die Initialisierung und freigabe weglassen kann. Damit können einige Destruktoren und manchmal auch ein Konstruktor wegfallen.eine klasse soll einen zweck erfüllen. das ist sicher nicht der fall, wenn du solche probleme hast.
Ein weiter vorteil (3):
Man spart Verwaltungsaufwang: Wenn der dynamischer Speicher an einer Stelle angefordert und an einer anderen Stelle freigegeben wird oder an verschieden Stellen benötigt wird, kann ein guter Smart-Pointer den ganzen Verwaltungskram unnötig machen.das ist in wirklichkeit dein angeblicher vorteil(2). und mit dem selben fehler behaftet.
sobald du mit resourcen wie zb bei DirectX arbeitest, wirste einen gut organisierten intrusive_ptr lieben lernen. das hat nix mit mehreren aufgaben zu tun, da reicht schon das simple verwenden der schnittstelle, um das problem auszulösen.(aber das kann man fast unter die kategorie designfehler zählen, da es nicht sein kann, dass man bei einer integrierten referenzzählung selbst den release knopf drücken muss)
Was aber unstrittig ist ist, dass wenn ich resourcen benutze, ich immer irgendwo einen destructor habe, der sich um die verwaltung dieser kümmern muss. Wenn ich es für sinnvoll erachte eine art referenzzählung zu haben, kann ich diese aufgabe an einen shared_ptr deligieren, und spare mir an anderen stellen einen destructor. Auch brauch ich mich nicht darum zu kümmern, dass eine resource irgendwann ungeplant freigegeben wird.
Aber davon mal abgesehen:
Wenn man deiner logik folgt, dann macht es folglich auch keinen sinn mehr, sowas zu schreiben:class Foo { private: std::vector<Bar> foobar; };denn, ob ich einen smart_ptr oder einen vector benutze ist ja echt jacke wie hose, da beide nur die aufgaben der resourcen verwaltung deligiert bekommen, und die selbst geschriebene Klasse damit eigentlich immernoch die aufgabe der resourcen verwaltung hat - nur halt nicht mehr offensichtlich für alle.
Die einzige möglichkeit, sich dieser logik zufolge gänzlich von der resourcenverwaltung in einer Klasse(die daten verwendet) abzukoppeln ist, nurnoch mit iteratoren auf die daten zu arbeiten, und die daten irgendwo ausserhalb zentralisiert zu verwalten. Aber selbst dann hat man immernoch das problem, dass man sich irgendeinen sinnigen weg ausdenken muss, wann nun die daten effektiv wieder freigegeben werden können/sollen/müssen.
(imho ist das, wenn ich darüber nachdenke, in vielen fällen auch ein sinnvoller ansatz, da normalerweise erheblich mehr objekte daten verwenden, als erstellen oder löschen. Das könnte in vielen fällen äußerst saubere lösungen ergeben. Das problem des resourcenfreigebens stellt sich eigentlich erst in umgebungen, bei denen daten zu unterschiedlichen zeitpunkten erstellt, benutzt und wieder gelöscht werden.)//Edit ich hab so das gefühl, dass ich am ende meines postes grad entweder einen sehr fundamental richtigen gedankengang hatte, oder nur absoluten scheiss verzapft hab, weil ich volkard mal wieder falsch verstanden hab(in dem fall würd ich mich aber über eine auflösung des rätsels freuen)

-
otze schrieb:
sobald du mit resourcen wie zb bei DirectX arbeitest [...] designfehler
kein widerspruch. je schlechter der code, desto stärker zieht's einen zu solchen mitteln. nun ist es eine überlegung wert, DirektX in einer eigenen engine zu verpacken.
Was aber unstrittig ist ist, dass wenn ich resourcen benutze, ich immer irgendwo einen destructor habe, der sich um die verwaltung dieser kümmern muss.
jo.
Wenn ich es für sinnvoll erachte eine art referenzzählung zu haben, kann ich diese aufgabe an einen shared_ptr deligieren, und spare mir an anderen stellen einen destructor.
wenn du referenzzählung für sinnvoll erachtest und vorher nochmal hier nachgefragt hast, ob es nicht anders ginge. smartpointer haben ihren platz. zum beispiel bei mehren verbrauchern (wo kommt das nochmal vor?) *und* aus irgendwelchen gründen keinem dispatcher davor, der jedem verbraucher ein paket gibt, sondern jeder verbraucher kann in der paketekiste wühlen und jederzeit eins rausnehmen und löschen *und* es gibt keine paketkiste, die als besitzer gehen würde, sondern... keine ahnung. irgendwie entgleitet mir das problem, das ich konstruieren will, wo man smartpointers braucht, ohne daß man ne mißratene lib als begründung hat.
Aber davon mal abgesehen:
Wenn man deiner logik folgt, dann macht es folglich auch keinen sinn mehr, sowas zu schreiben:class Foo { private: std::vector<Bar> foobar; };denn, ob ich einen smart_ptr oder einen vector benutze ist ja echt jacke wie hose, da beide nur die aufgaben der resourcen verwaltung deligiert bekommen, und die selbst geschriebene Klasse damit eigentlich immernoch die aufgabe der resourcen verwaltung hat - nur halt nicht mehr offensichtlich für alle.
wenn
typedef vector<Bar> Foo;gemeint ist, soll man das auch schreiben.
vieleicht meinste jaclass Weinkeller { private: vector<Wein> keller; public: void einlagern(Wein const& w){ keller.push_back(w); } ... };diese klasse ist ein wrapper um den vector, der die implementierungsbegriffe wie push_back in die modellbegriffe wie einlagern umsetzt. die hat sehr wohl einen zweck und eine lebensberechtigung.
Die einzige möglichkeit, sich dieser logik zufolge gänzlich von der resourcenverwaltung in einer Klasse(die daten verwendet) abzukoppeln ist, nurnoch mit iteratoren auf die daten zu arbeiten, und die daten irgendwo ausserhalb zentralisiert zu verwalten.
ich hab jetzt nicht ganz mitgekriegt, warum du annimmst, ich würde RAII und destruktoren nicht mögen. klar mag ich die.
erinnern wir uns an diesen fetzen:
void main() { for(int i = 0; i<123;i++) { int* ptr = NULL; ptr = CallInputPoint(); // stell dir hier ein break vor! delete ptr; ptr = NULL; } }erstmal klären, int main() und nutzlose NULL-zuweisungen weg.
int main() { for(int i=0;i<123;i++) { int* ptr = CallInputPoint(); // stell dir hier ein break vor! delete ptr; } }dann kann ich mir nicht vorstellen, daß da wirklich ein int* benötigt wird.
int main() { for(int i=0;i<123;i++) { InputPoint* ptr = CreateInputPoint(); // stell dir hier ein break vor! delete ptr; } }und schwupps, MUSS MAN EINEN SMARTPOINTER NEMEN, wie ich im folgenden genau vormache.
int main() { for(int i=0;i<123;i++) { InputPoint i; } }mist, schon wieder ist mir das beispiel entglitten und ich brauchte keine smartpointers.
-
volkard schrieb:
Wenn ich es für sinnvoll erachte eine art referenzzählung zu haben, kann ich diese aufgabe an einen shared_ptr deligieren, und spare mir an anderen stellen einen destructor.
wenn du referenzzählung für sinnvoll erachtest und vorher nochmal hier nachgefragt hast, ob es nicht anders ginge. smartpointer haben ihren platz. zum beispiel bei mehren verbrauchern (wo kommt das nochmal vor?) *und* aus irgendwelchen gründen keinem dispatcher davor, der jedem verbraucher ein paket gibt, sondern jeder verbraucher kann in der paketekiste wühlen und jederzeit eins rausnehmen und löschen *und* es gibt keine paketkiste, die als besitzer gehen würde, sondern... keine ahnung. irgendwie entgleitet mir das problem, das ich konstruieren will, wo man smartpointers braucht, ohne daß man ne mißratene lib als begründung hat.
ich hatte letztens sowas:
Ein Objekt der Klasse "File" besitzt einen shared_ptr auf eine Klasse handle. Handle besitzt einfachste methoden um zeugs aus einer datei zu lesen.
Nun hat File die methode: "getReadAccess". Diese methode erzeugt jedes mal ein Objekt, welches auch einen shared_ptr auf handle hat, jedes mal ein stück der datei puffert, und dann aus dem gepufferten teil liest. Hat den vorteil, dass man aus verschiedenen stellen der datei "gleichzeitig" lesen kann- man stelle sich hier ein archiv vor, in dem verschiedenste daten gespeichert sind.
Wenn file nun zerstört werden würde, wäre es schlecht, wenn auch gleichzeitig der lesezugang flöten ginge, wenn *doch* noch irgendwer was lesen muss, und so wird handle erst zerstört, wenn keine referenzen mehr drauf existieren.Read foo(){ File f("bar.txt"); return f.getReadAccess(); }Die einzige möglichkeit, sich dieser logik zufolge gänzlich von der resourcenverwaltung in einer Klasse(die daten verwendet) abzukoppeln ist, nurnoch mit iteratoren auf die daten zu arbeiten, und die daten irgendwo ausserhalb zentralisiert zu verwalten.
ich hab jetzt nicht ganz mitgekriegt, warum du annimmst, ich würde RAII und destruktoren nicht mögen. klar mag ich die.
Was mir nicht in den Kopf gehen will ist, dass du einerseits wrapper um einen vector ganz toll findest, aber den vorschlag, einen smart_pr zu benutzen, um das löschen einen dynamischen objekts zu automatisieren für schlecht hälst, und sofort ein Problem im design unterstellst. Im endeffekt machen beide objekte ja genau das gleiche- nur mit unterschiedlichem ansatz.
Natürlich könnte es sein, dass du smart_ptr ansich nicht für schlecht hälst, sondern nur die art ihrer benutzung. vielleicht hälst du es für unangemessen, dass son ding inmitten des Codes einfach so herumschwirrt, sondern du lieber sowas hättest:
//ich bleib einfach ganz nah am ursprungsbeispiel //ich nehme einfach mal an, dass der ptr als rückgabewert sein muss, weil das ganze ding polymorph ist class InputPoint{ private: smart_ptr<BaseInputPoint> point; public; InputPoint(){ point = CreateInputPoint(); } }; int main() { for(int i=0;i<123;i++) { InputPoint i; } }Und da stimm ich dir auch zu. Diese kleinen Helferlein müssen ein Mittel sein, welches RAII ermöglicht. Nicht mehr.
-
otze schrieb:
class InputPoint{ private: smart_ptr<BaseInputPoint> point; public; InputPoint(){ point = CreateInputPoint(); } };wird aber dan doch zu
class InputPoint{ private: BaseInputPoint* point; public; InputPoint(){ point = CreateInputPoint(); } ~InputPoint(){ DestroyInputPoint(point); } };selten, daß man die InputPoints auch noch kopieren will.
Diese methode erzeugt jedes mal ein Objekt, welches auch einen shared_ptr auf handle hat, jedes mal ein stück der datei puffert
ja, hier sind smartpointers sinnvoll.
-
Genau dieses letzte Umschreiben leuchtet mir nicht ein. Warum sollte ich der Klasse nochmal explizit einen Destructor spendieren, der den Pointer löscht, statt diese Aufgabe mit z. B. einem scoped_ptr eben genau diesem zu überlassen? Der scoped_ptr ist einmal implementiert und nimmt mir diese Arbeit doch gerade ab. Zumal es ja auch noch möglich wäre, dass ich im Konstruktor nach dem Allokieren von point weitere Operationen durchführen muss, die Exceptions werfen könnten (oder ist sowas in deinen Augen für Konstruktoren schon ein absolutes Tabu?). Dann müsste ich diese auch noch abfangen und im Konstruktor in dem Fall ebenfalls delete schreiben. Alles Aufwand, den mir scoped_ptr gerade abnimmt.
-
kwaart schrieb:
Genau dieses letzte Umschreiben leuchtet mir nicht ein. Warum sollte ich der Klasse nochmal explizit einen Destructor spendieren, der den Pointer löscht, statt diese Aufgabe mit z. B. einem scoped_ptr eben genau diesem zu überlassen? Der scoped_ptr ist einmal implementiert und nimmt mir diese Arbeit doch gerade ab. Zumal es ja auch noch möglich wäre, dass ich im Konstruktor nach dem Allokieren von point weitere Operationen durchführen muss, die Exceptions werfen könnten (oder ist sowas in deinen Augen für Konstruktoren schon ein absolutes Tabu?). Dann müsste ich diese auch noch abfangen und im Konstruktor in dem Fall ebenfalls delete schreiben. Alles Aufwand, den mir scoped_ptr gerade abnimmt.
ich dachte, die klasse, die weitere initialisierungen macht, besitzt einen InputPoint als attribut.
-
class InputPoint{ private: BaseInputPoint* point; public; InputPoint(){ point = CreateInputPoint(); } ~InputPoint(){ DestroyInputPoint(point); } };hier fänd ich nen smart_ptr besser, dem man nen eigenen dtor spendieren kann(shared_ptr oder noch speziellere).
-
otze schrieb:
class InputPoint{ private: BaseInputPoint* point; public; InputPoint(){ point = CreateInputPoint(); } ~InputPoint(){ DestroyInputPoint(point); } };hier fänd ich nen smart_ptr besser, dem man nen eigenen dtor spendieren kann(shared_ptr oder noch speziellere).
wird dabei der smartpointer zufällig doppelt so groß? und wie langsam wird er? und dann greife ich auf manche attribute mit . zu und auf manche mit -> und das nur, weil ich unbedingt nen smartpointer nehmen wollte. seltsame welt. warum können wir nicht einfach smartpointers meiden und nur nehmen, wenn sie unabdingbar sind?