smartpointer (auto_ptr) Vs 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?
-
volkard schrieb:
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?
nö, doppelt so groß muss er net werden, geht ja alles mit templates. und du brauchst kein attribut auf das du mit . zugreifen musst.
-
otze schrieb:
nö, doppelt so groß muss er net werden, geht ja alles mit templates.
ich hab wohl meinen spieltrieb verloren und muß nicht mehr alles mit templates machen.
und du brauchst kein attribut auf das du mit . zugreifen musst.
jo, wenn man entsprechende klassen baut, kann man immer statt
vector<size_t> sizeseinfach
Attribute<vector<size_t> > sizesschreiben. das hat den vorteil, daß man konsequent sizes->push_back(...) schreiben darf, egal ob das ding hinter einem zeiger liegt oder in einem Attribute. außerdem kann man furchtbar gut pimpln mit sowas.
-
volkard schrieb:
und du brauchst kein attribut auf das du mit . zugreifen musst.
jo, wenn man entsprechende klassen baut, kann man immer statt
vector<size_t> sizeseinfach
Attribute<vector<size_t> > sizesschreiben. das hat den vorteil, daß man konsequent sizes->push_back(...) schreiben darf, egal ob das ding hinter einem zeiger liegt oder in einem Attribute. außerdem kann man furchtbar gut pimpln mit sowas.
oh, das meisnt du. ich dachte du meinst, dass man einerseits mit op-> des smart_ptr auf werte zugreifen kann, und mit op. auf werte des smart_ptr selber.
So wie du das gemeint hast, ist das aber auch kein problem. denn sinniger weise kann ein smart_ptr nur dort eingesetzt werden, wo ein zeiger benutzt wird. Das ist sozusagen die notwendige bedingung für die verwendung eines smart_ptr. Und ja pimpln kann man damit wirklich fein(dann aber mit nem null kosten scoped_ptr)
-
otze schrieb:
Und ja pimpln kann man damit wirklich fein(dann aber mit nem null kosten scoped_ptr)
noch feiner (im sinne von nullkosten), wenn zum pimpln kein new/delete benutzt werden müßte. das macht so eine Attribute-klasse vielleicht so, daß im release-modus innen ein echtes objekt steht (und mehr wird inkludiert und das compilieren dauert ewig) und im debug-modus nur ein zeiger.
-
otze schrieb:
So wie du das gemeint hast, ist das aber auch kein problem. denn sinniger weise kann ein smart_ptr nur dort eingesetzt werden, wo ein zeiger benutzt wird. Das ist sozusagen die notwendige bedingung für die verwendung eines smart_ptr.
dieser BaseInputPoint* ist nur ein zeiger wegen der polymorphie! er ist nicht ein zeiger, weil das der anwendungslogik entsprechen würde. er ist nicht sowas wie
class Mensch{ Mensch* vater; Mensch* mutter; };, wo sinnigerweise das objekt andere objekte "kennt", sondern hier besitzt es das andere objekt ganz heftig. also ist layering angesagt, schlichtes attribut. das ermögliche ich mit meiner kleinen klasse. zeigereien sind hier nur störendes implementierungsdetail.
das wird sich natürlich ändern, wenn wir anfangen, generell nur mit -> auf attribute zuzugreifen. den plan hab ich 1990 schonmal verfolgt und er wurde nix, aber wenn ich mich recht erinnere, nur wegen dummer compiler. müßte mal das problem beleuchten, wie man initialisiererliste und so gescheit hinkriegt.
-
dieser BaseInputPoint* ist nur ein zeiger wegen der polymorphie! er ist nicht ein zeiger, weil das der anwendungslogik entsprechen würde.
dagegen sag ich auch nichts. Wenn er kein zeiger wäre(und das ist er nur dann, wenn es unbedingt nötig ist), müsste man sich in keiner form fragen, ob da ein smart_ptr angebracht währe.
Diesen malus der sprache, dass man unbedingt mit op-> auf alles zugreifen muss, was mit new angelegt wurde, kann man nur schlecht einem Objekt zu lasten legen, das versucht einen teil des problems(nämlich das löschen) zu beheben.um nochmal daus einem deiner letzten posts zu zitieren:
und dann greife ich auf manche attribute mit . zu und auf manche mit -> und das nur, weil ich unbedingt nen smartpointer nehmen wollte.
Nein. du greifst nur an der stelle mit op-> zu, wo du immer mit op-> zugreifen würdest. Nämlich dort, wo es einen zeiger gibt. Wenn du keinen zeiger hast, brauchst du dir nicht zu überlegen, ob du einen smart_ptr brauchst. Der smart_ptr ändert das verhalten, aber nicht die syntax.
das wird sich natürlich ändern, wenn wir anfangen, generell nur mit -> auf attribute zuzugreifen. den plan hab ich 1990 schonmal verfolgt und er wurde nix, aber wenn ich mich recht erinnere, nur wegen dummer compiler. müßte mal das problem beleuchten, wie man initialisiererliste und so gescheit hinkriegt.
du suchst wahrscheinlich eher die überladbarkeit des (op.) . Er drückt nämlich am besten das besitzverhältnis aus. Dann würde man mit op-> nurnoch auf objekte zugreifen die man "kennt", und mit op. nurnoch auf Objekte die man hat.
Um das mal auf smart_ptr zu übertragen, wär das wahrscheinlich die perfekte verbesserung für scoped_ptr und auto_ptr, denn die drücken ein unheimlich starkes besitzverhältnis aus(einmal mit übertragbarkeit, einmal ohne). Der shared_ptr/intrusive_ptr würde dann wieder das ausdrücken, was er eigentlich ist. eine "kennt" beziehung.
-
Wird das jetzt hier zu Pimp my Smartpointer ?
:p