Wie verwende ich "delete" richtig?



  • es stürzt ab weil du zwar das löschst wodrauf der pointer zeigt, aber nicht den pointer auf null setzt (so wie du glaubst, denke ich mal). denn zeile 31 setzt nur den pointer in vec1 auf 0 und nicht den in vec2, sprich am ende der ersten schleife, steht in vec1 nur noch 0, 0, 0... aber in vec2 noch ehemals gültige addressen. wenn du nun delete auf etwas aus vec2 anwendest löschst du schon freigegebenen speicher --> crash

    wie man mit sowas umgeht?
    tja, am besten gar nicht (daran sieht man dass java viel unkomplizierter ist). aber manchmal braucht man natürlich mehrere verweise auf ein und das selbe datum daher lohnt sich bei sowas ein std::shared_ptr<>, der zählt mit wie viele zeiger auf dein ding zeigen und löscht dieses automatisch wenn der letzte zeiger aufhört zu existieren

    wenn du aber sowas nicht benutzen willst (aus welchen nict-trifftigen gründen auch immer), dann musst du schon sehr genau aufpassen und manuell drauf achten, wer wann wie was freigibt und noch innebehält. und genau das ist der grund für die meisten fehler...



  • nightrider schrieb:

    Hallo!

    Ich komme aus der Java-Welt. Da muss man sich nicht ums Aufräumen kümmern.
    Mein Problem ist nun folgendes: Ich habe 2 Vektoren vec1, vec2. vec1 speichert 2 Elemente vom Typ A, vec2 Speichert ein beliebiges Element, das von vec1 kommt und noch ein neues Element. (wenn ich von Element spreche meine ich natürlich Pointer)

    Meine Frage ist:
    Warum speicherst Du in den Vektoren Pointer? Machst Du das, weil Du es von Java nicht anders kennst, gibt es einen wichtigen Grund dafür oder möchtest Du einfach ein wenig experimentieren?
    Weißt Du überhaupt, dass Du auch Deine Objekte selbst in die Vektoren stecken kannst, und Dich dann (wie in Java auch) nicht mehr ums Aufräumen kümmern musst?



  • In C++ muss man sich auch nur ums Aufräumen kümmern, wenn man es so komisch macht 😕

    Man könnte es auch einfach so machen:

    #include <vector>
    using namespace std;
    
    class A {};
    
    void foo1(vector<A> &vec) 
    {
    	vec.push_back(A());
    	vec.push_back(A());
    }
    
    void foo2(vector<A> &vec1, vector<A> &vec2) 
    {
    	vec2.push_back(A());
    	vec2.push_back(vec2.front());
    }
    
    int main(int argc, char** argv)
    {
       vector<A> vec1;
       vector<A> vec2;
       foo1(vec1);
       foo2(vec1, vec2);
    }
    

    Solltest du es aus irgendeinem Grund mit Pointern machen müssen:
    Es stürzt ab, weil du in beiden Vektoren auf dasselbe Objekt zeigst. Nachdem du vec1[0] gelöscht hast, zeigt vec2[1] auf Speicher, der eventuell schon von einem anderen Programm oder so genutzt wird. Und wenn du diesen dann erneut löschen möchtest, kommt natürlich nichts Gutes dabei heraus.


  • Mod

    if(e != 0) { delete e; e = 0;}
    

    Dies ist doppelt Unsinn:
    1. Einen Nullpointer darfst du gerne einem delete vorsetzen. Es ist definiert, dass nichts besonderes passiert.
    2. Das Nullsetzen macht nur dann Sinn, wenn die 0 in deinem Programm auch etwas bedeutet (z.B. Ende der List in verketteten Listen). Tut sie aber nicht. Stattdessen werden potentiell Fehler verschleppt, wenn so ein Zeiger fehlerhafterweise doppelt gelöscht wird und das Programm trotzdem weiterläuft (siehe Punkt 1), ohne dass man den Fehler beim Testen durch einen Absturz bemerkt.

    Hier hast du sogar Glück, dass du die Zeiger nicht richtig auf 0 gesetzt hast und dadurch hier gelandet bist. Sonst wäre Punkt 2 eingetreten und du hättest womöglich so weitergemacht.

    Die "richtige" Lösung hat Wurstinator gezeigt.



  • Wow, sehr schnell sehr viel Antworten. Danke euch.
    Also dass man nicht nur Referenzen speichern kann ist mir klar. Ich habs auch vorher so gemacht, dass ich Instanzen von A in den Vektoren gespeichert habe.
    Nur erstens ist A verdammt groß und kopieren deswegen langsam. Zweitens brauche ich nur eine Instanz von A, auf die dann verwiesen wird. Also mit Pointern zu arbeiten finde ich hier prinzipiell richtig.
    Dann werde ich mir Smart Pointer anschauen müssen. 🙂
    Wie hat man denn bitte vor 10 Jahren programmiert?? Ständig selbst Referenzen gezählt? Wie mühsam ist das denn bitte?
    Also danke für eure Hilfe! 🙂



  • SeppJ schrieb:

    if(e != 0) { delete e; e = 0;}
    

    Dies ist doppelt Unsinn:
    1. Einen Nullpointer darfst du gerne einem delete vorsetzen. Es ist definiert, dass nichts besonderes passiert.
    2. Das Nullsetzen macht nur dann Sinn, wenn die 0 in deinem Programm auch etwas bedeutet (z.B. Ende der List in verketteten Listen). Tut sie aber nicht. Stattdessen werden potentiell Fehler verschleppt, wenn so ein Zeiger fehlerhafterweise doppelt gelöscht wird und das Programm trotzdem weiterläuft (siehe Punkt 1), ohne dass man den Fehler beim Testen durch einen Absturz bemerkt.

    Hier hast du sogar Glück, dass du die Zeiger nicht richtig auf 0 gesetzt hast und dadurch hier gelandet bist. Sonst wäre Punkt 2 eingetreten und du hättest womöglich so weitergemacht.

    Die "richtige" Lösung hat Wurstinator gezeigt.

    1. Verstehe ich jetzt nicht. Du meinst, dass das if() sinnlos ist? Wenn ja, dann glaube ich dir das.
    2. Warum sollte 0 nichts bedeuten? Einen Zeiger 0 zu setzen gehört wohl zum guten Stil?

    Die Lösung von Wurstinator verwendet kein new/delete. Wie gesagt, ich brauche nur eine Instanz. Kopieren wäre zu zeitaufwändig.



  • 1. Verstehe ich jetzt nicht. Du meinst, dass das if() sinnlos ist? Wenn ja, dann
    ...

    Ja, es ist sinnlos.

    2. Warum sollte 0 nichts bedeuten? Einen Zeiger 0 zu setzen gehört wohl zum guten Stil?

    Wie SeppJ erklärte gehört es eben nur in Ausnahmefällen zum guten Stil. In den meisten Fällen gehört es zum schlechten Stil weil Fehler verschleiert werden können. (Edit: das bezieht sich auf Zeiger 0 setzten nach dem delete.)

    Die Lösung von Wurstinator verwendet kein new/delete. Wie gesagt, ich brauche nur eine Instanz. Kopieren wäre zu zeitaufwändig.

    Ob das new/delete oder kopieren zeitaufwändiger ist würde ich einem Profiler überlassen. Als default einfach kopieren.



  • nightrider schrieb:

    Die Frage ist nun, wie räumt man das auf?

    Das ist eigentlich die falsche Frage; Die richtige Frage lautet: Wie macht man es (design-technisch) besser? Und dazu müsste man wahrscheinlich ein bisschen mehr darüber wissen, was du da eigentlich machen willst.

    In C++ spielt beim Design die "Besitz-Relation" zwischen Objekten und anderen Objekten oder Ressourcen eine wichtigere Rolle. Mit Besitz ist dann so etwas gemeint, wie die Verantwortung bzgl Verwaltung der Lebenszeit einer Ressource (muss nicht unbedingt Speicher sein). Rohe Zeiger fühlen sich für nix verantwortlich. Verschwindet so ein Zeiger, passiert nichts mit dem worauf er zeigte. Wenn du dir also mit einem rohen Zeiger die Adresse eines Objekts merkst, welches sonst keinem "Besitzer" zugeordnet ist, dann musst du das Objekt manuell löschen. Dann hast du aber eigentlich schon etwas falsch gemacht, design-technisch gesehen; denn dann hast du den falschen Typ (roher Zeiger) gewählt, weil er sich nicht für das Objekt verantwortlich fühlt und es viel besser und weniger fehleranfällig ist, wenn man frühstmöglich die Verantwortung über eine Ressource an ein Objekt abtritt.

    Als Java-Flüchtling wirst du erstmal viel new und new[] verwenden wollen, weil du es nicht anders kennst. C++ zwingt dir aber keine solche Indirektion (wie die über Java-Referenzen) auf. Code wie

    int main()
    {
      Foo *p = new Foo;
      ...
      delete p;
    }
    

    ist nicht nur schwachsinnig, sondern auch unsicher bzgl Ausnahmen (ggf wird dieses delete gar nicht ausgeführt, wenn es vorher schon ein return oder ein throw gibt). Stattdessen schreiben wir

    int main()
    {
      Foo f;
      ...
    }
    

    damit das Foo-Objekt im automatischen Speicher lebt und automatisch wieder zerstört wird, wenn der Gültigkeitsbereich verlassen wird. Genauso, als wenn ich lokal eine double-Variable angelegt hätte. Die muss ich ja auch nicht per delete löschen.

    Ebenso verhält es sich beim Klassen-Design. Es gibt im Prinzip drei Arten von Beziehungen zwischen Objekten:

    1. Objekt x ist logisch und pysisch Teil von Objekt y. y besteht aus x.

    class Y
    {
      ...
      X x;
      ...
    };
    

    Hier muss man innerhalb eines Y-Objekts x nicht per new anlegen und muss es auch nicht löschen. Wenn das Y-Objekt zerstört wird, wird auch automatisch das Element x zerstört. Das ist genauso wie die Sache mit dem automatischen Speicher.

    2. Objekt x ist weder logisch noch pysisch Teil von Objekt y. y kennt x.

    class Y
    {
      ...
      X *pointer_to_x;
      ...
    };
    

    Hier kennt das Y-Objekt nur ein X-Objekt, fühlt sich aber nicht dafür verantwortlich, was die Löschung angeht.

    3. Objekt x ist zwar logisch aber nicht physisch ein Teil von y. y besitzt x, x wird aber außerhalb von y gespeichert.

    class Y
    {
      ...
      std::unique_ptr<X> pointer_to_x;
      ...
    };
    

    Man kann hier auch einen rohen Zeiger verwenden und dann die Kopier/Zerstörsemantik der Klasse Y durch selbstgeschriebene Kopieroperationen und Destruktor anpassen. So wird beispielsweise unique_ptr und vector implementiert. Intern verwenden die auch nur einen rohen Zeiger, überschreiben aber das Kopier/Zerstör-Verhalten. Solange wir aber einen passenen Elementtypen für unsere Klasse wählen können (wie z.B. unique_ptr) ist das nicht nötig, einen eigenen Destruktor zu schreiben oder sowas. Warum ist dieser Fall noch interessant, obwohl wir schon mit dem ersten eine Besitz-Beziehung haben? Na, vielleicht ist das Element optional, oder man will polymorphes Verhalten, X könnte beispielsweise eine abstrakte Basisklasse sein. Das geht dann immer nur über eine Art von Indirektion.

    Wenn du jetzt beim Design drauf achtest, dass jede Ressource, die akquiriert wird, auch einem Besitzer zugeordnet ist, der sich darum kümmert, brauchst du dir nicht mehr den Kopf darüber zerbrechen, wo was gelöscht werden muss. Allerdings dauert das glaub'ich eine Weile, bis man dieses Denken intus hat.

    Stephan T. Lavavej hat so schön gesagt: "ownership is a directed acyclic graph". Und diese Sichtweise ist sicher ganz praktisch. Solange die "x kümmert sich um das Freigeben von y"-Beziehung ein solcher azyklischer Graph ist, und die "Wurzeln" nicht im Freispeicher (Heap) anfangen, ist eigentlich alles in Ordnung. Ein Ressourceleck wird es dann per Design nicht mehr geben.

    Wenn du Objekte direkt in STL-Conainern speichern kannst, warum nicht? Bei der direkten Speicherung von Objekten in STL-Containern ist darauf zu achten, dass diese ggf auch mal im Speicher "verschoben" werden. Prominentes Beispiel: vector<MyClass>. Fügst du mehr und mehr Elemente hinzu, müssen diese in immer größere Speicherbereiche "verschoben" werden. Es ist also meist eine schlechte Idee, sich die Adressen (Zeiger, Iteratoren) von in einem vector lebenden Objekten für eine längere Zeit zu merken. Eine vernünftige Standardbibliotheks-Doku verrät dir genau, wann wo was invalidiert werden könnte.

    Wenn du Indirektion brauchst, dann verwende rohe Zeiger für "x kennt y"-Beziehungen, std::unique_ptr für logische "x besitzt y"-Beziehungen (unique ownership) und std::shared_ptr für logische "a,b,c,... teilen sich den Besitz von z"-Beziehungen (shared ownership).

    Als Java-Umsteiger, der gerade darüber nachdenkt, new/delete zu verwenden, solltest du da echt doppelt und dreifach darüber nachdenken, ob new/delete an der Stelle wirklich eine gute Idee ist. Selbst für Objekte, die du über shared_ptr managst, brauchst du kein new mehr. Und das delete ja sowieso nicht:

    shared_ptr<MyClass> sp = make_shared<MyClass>(42);
    // oder auch abkürzend dank auto aus C++11:
    // auto sp = make_shared<MyClass>(42);
    

    Für unique_ptr muss man aktuell noch new von Hand aufrufen. Aber ich glaube, hier bekommen wir in Zukunft auch noch ein make_unique, welches durchaus nochmal Vorteile gegenüber beispielsweise unique_ptr<int>(new int) hinsichtlich Ausnahme-Sicherheit hat.

    Upps ... das ist doch alles viel länger und ausführlicher geworden, als ich eigentlich wollte. Na, dann bookmarke ich mir den Text mal und recycle ihn beim nächsten mal wieder ... ^^

    Cheers!
    kk



  • theta schrieb:

    1. Verstehe ich jetzt nicht. Du meinst, dass das if() sinnlos ist? Wenn ja, dann
    ...

    Ja, es ist sinnlos.

    2. Warum sollte 0 nichts bedeuten? Einen Zeiger 0 zu setzen gehört wohl zum guten Stil?

    Wie SeppJ erklärte gehört es eben nur in Ausnahmefällen zum guten Stil. In den meisten Fällen gehört es zum schlechten Stil weil Fehler verschleiert werden können. (Edit: das bezieht sich auf Zeiger 0 setzten nach dem delete.)

    Es ist nur so, dass mindestens 50% der C++ Tutorials vorschlagen, den Zeiger nach dem delete auf 0 zu setzen. Aber ich glaubs dir und werds nicht mehr machen.

    Die Lösung von Wurstinator verwendet kein new/delete. Wie gesagt, ich brauche nur eine Instanz. Kopieren wäre zu zeitaufwändig.

    Ob das new/delete oder kopieren zeitaufwändiger ist würde ich einem Profiler überlassen. Als default einfach kopieren.

    Meine Klasse speichert Koordinaten von 3D-Objekten. Sagen wir ein 3D-Objekt hat 1000 Koordinaten. Jede Koordinaten hat 3 floats. Das heißt in der Klasse sind 3000 floats gespeichert.
    Was ist jetzt schneller zu kopieren? Ein 32bit Zeiger oder 3000 floats? Um die Frage zu beantworten brauche ich keinen Profiler. 🙂

    @krümelkacker: Ein sehr schöner Text. Du hast recht, in C++ muss man sich sicher stärker darüber Gedanken machen, wem was zugeordnet ist und wer was auch wieder löscht. Dass allokierte Speicher während der Programmlaufzeit verschoben werden kann und dadurch meine Pointer ungültig werden wusste ich gar nicht. Das schockiert mich jetzt.

    Ich schreibt alle so, als würdet ihr new/delete so gut wie nie verwenden bzw. als schlecht empfinden.
    Wie gesagt, wenn Rechenzeit/Speicher wertvoll ist, dann macht es wenig Sinn, Objekte ständig zu kopieren. Natürlich kann man ein Objekt am Stack anlegen und dann mit Zeigern darauf verweisen. Aber das ist auch nicht immer sinnvoll. (Wenn wer ein Beispiel sehen will wann das nicht sinnvoll ist, dann zeig ich gern eins 😉 )

    Noch ein Beispiel, wo man um ein new nicht herumkommt.

    A* foo() {
      int a, b;
      cin >> a;
      cin >> b;
      return new A();
    }
    

    Natürlich könnte es auch so aussehen:

    A a;
    foo(&a);
    
    void foo(A* a) {
      cin >> a->a;
      cin >> a->b;
    }
    

    Aber wie sieht das aus? Sehr hässlich.

    Grüße 🙂



  • Oder so:

    A a;
    foo(a);
    
    void foo(A& a) {
      cin >> a.a;
      cin >> a.b;
    }
    

    Und das sieht gut aus.
    Dein erstes Beispiel sollte wohl new A(a,b) zurückgeben.



  • nightrider schrieb:

    Was ist jetzt schneller zu kopieren? Ein 32bit Zeiger oder 3000 floats? Um die Frage zu beantworten brauche ich keinen Profiler. 🙂

    na dann miss mal. Wann werden die Objekte im vector denn kopiert? Nur dann, wenn du den vector selbst kopierst (was so gut wie nie vorkommt) und wenn der vector neuen Speicher alloziert und den alten wegwirft. Diese beiden Operationen sind Systemaufrufe und brauchen länger als ein paar Bytes zu kopieren. Aber auch sonst wäre es keine gute Idee, sich das Leben so viel schwerer mit Pointern zu machen, nur um ein paar Millisekunden - wenn überhaupt - einzusparen. Ein paar Kilobytes sind gar nichts für heutige Rechner.

    Ich schreibt alle so, als würdet ihr new/delete so gut wie nie verwenden bzw. als schlecht empfinden.

    Korrekt.

    Wie gesagt, wenn Rechenzeit/Speicher wertvoll ist, dann macht es wenig Sinn, Objekte ständig zu kopieren.

    Mach dein Programm erst mal funktionsfähig. Wenn es zu langsam ist, benutze einen Profiler und prüfe erst mal, ob dein Algorithmus nicht schlecht ist.

    Noch ein Beispiel, wo man um ein new nicht herumkommt.

    A* foo() {
      int a, b;
      cin >> a;
      cin >> b;
      return new A();
    }
    

    Natürlich könnte es auch so aussehen:

    A a;
    foo(&a);
    
    void foo(A* a) {
      cin >> a->a;
      cin >> a->b;
    }
    

    Aber wie sieht das aus? Sehr hässlich.

    Deshalb gibts da noch die dritte Variante:

    A foo() {
      A a;
      cin >> a->a;
      cin >> a->b;
      return a;
    

    Keine hässlichen Zeiger und genauso schnell wie deine beiden Varianten, weil der Compiler die Kopie wegoptimiert.


  • Mod

    nightrider schrieb:

    Meine Klasse speichert Koordinaten von 3D-Objekten. Sagen wir ein 3D-Objekt hat 1000 Koordinaten. Jede Koordinaten hat 3 floats. Das heißt in der Klasse sind 3000 floats gespeichert.
    Was ist jetzt schneller zu kopieren? Ein 32bit Zeiger oder 3000 floats? Um die Frage zu beantworten brauche ich keinen Profiler. 🙂

    Äpfel und Birnen. Falls du eine echte (deep) Kopie brauchst, wirst du auch alles kopieren müssen. Wenn keine Kopie benötigt wird, muss nicht einmal ein Zeiger kopiert werden. Ansonsten ist die Aussage schlicht zu unspezifisch, um als Argument für oder gegen irgendetwas dienen zu können.

    nightrider schrieb:

    Wie gesagt, wenn Rechenzeit/Speicher wertvoll ist, dann macht es wenig Sinn, Objekte ständig zu kopieren.

    Sinnlos ist diese Aussage, weil völlig unspezifisch. Ich kann auch überhaupt nicht erkennen, wo die Implikation hier plötzlich herkommt. Wir kopieren doch nicht zum Spaß oder weil zufällig Speicher da ist und Zeit verschwendet werden kann, sondern deshalb, weil der verwendete Algorithmus das Anlegen einer Kopie erfordert.

    Noch ein Beispiel, wo man um ein new nicht herumkommt.

    A* foo() {
      int a, b;
      cin >> a;
      cin >> b;
      return new A();
    }
    

    Was soll dieser Code bezwecken?

    Natürlich könnte es auch so aussehen:

    A a;
    foo(&a);
    
    void foo(A* a) {
      cin >> a->a;
      cin >> a->b;
    }
    

    in C++ gibt es so etwas wie Value-return

    A foo() {
        int a, b;
        cin >> a >> b;
        return { a, b };
    }
    

    die anderen Varianten sind einfach nur Murks (und kein bisschen schneller).

    Kopierphobien machen Code nicht besser.


  • Mod

    nightrider schrieb:

    theta schrieb:

    1. Verstehe ich jetzt nicht. Du meinst, dass das if() sinnlos ist? Wenn ja, dann
    ...

    Ja, es ist sinnlos.

    2. Warum sollte 0 nichts bedeuten? Einen Zeiger 0 zu setzen gehört wohl zum guten Stil?

    Wie SeppJ erklärte gehört es eben nur in Ausnahmefällen zum guten Stil. In den meisten Fällen gehört es zum schlechten Stil weil Fehler verschleiert werden können. (Edit: das bezieht sich auf Zeiger 0 setzten nach dem delete.)

    Es ist nur so, dass mindestens 50% der C++ Tutorials vorschlagen, den Zeiger nach dem delete auf 0 zu setzen. Aber ich glaubs dir und werds nicht mehr machen.

    Deswegen wird hier ja auch dauernd vor diesen C++-Tutorials gewarnt. Die werden von Leuten geschrieben, die gerade C++ von A bis Z gelesen haben und denken, sie könnten nun anderen Leuten was beibringen.

    Die Lösung von Wurstinator verwendet kein new/delete. Wie gesagt, ich brauche nur eine Instanz. Kopieren wäre zu zeitaufwändig.

    Ob das new/delete oder kopieren zeitaufwändiger ist würde ich einem Profiler überlassen. Als default einfach kopieren.

    Meine Klasse speichert Koordinaten von 3D-Objekten. Sagen wir ein 3D-Objekt hat 1000 Koordinaten. Jede Koordinaten hat 3 floats. Das heißt in der Klasse sind 3000 floats gespeichert.
    Was ist jetzt schneller zu kopieren? Ein 32bit Zeiger oder 3000 floats? Um die Frage zu beantworten brauche ich keinen Profiler. 🙂

    Das meint krümelkacker nicht. Die wirkliche Frage ist, wie oft diese Kopieraktion stattfindet. Wenn du einmal am Programmanfang die Objekte erzeugst, wird das weniger als 0.001 % der Gesamtlaufzeit ausmachen, dafür kostet dich die Indirektion bei jedem Zugriff. Auch sonst kann (gerade in c++11) die ganze Kopiererei ganz wunderbar wegoptimiert werden. Deine Objekte werden ja sicherlich nicht selber eine sizeof von 3000 floats haben, sondern einen Verweis auf 3000 floats auf dem Heap haben, die dann gemoved werden können, was ungefähr die Kosten einer Pointerkopie hat.

    Die Leute hier wissen in der Regel schon, was sie tun. Hör auf die Leute mit tausenden von Beiträgen!

    Ich schreibt alle so, als würdet ihr new/delete so gut wie nie verwenden bzw. als schlecht empfinden.
    Wie gesagt, wenn Rechenzeit/Speicher wertvoll ist, dann macht es wenig Sinn, Objekte ständig zu kopieren. Natürlich kann man ein Objekt am Stack anlegen und dann mit Zeigern darauf verweisen. Aber das ist auch nicht immer sinnvoll. (Wenn wer ein Beispiel sehen will wann das nicht sinnvoll ist, dann zeig ich gern eins 😉 )

    Gerade wenn Rechenzeit wertvoll ist, ist new/delete das letzte was du willst. Automatische Objekte sind hundertmal und mehr schneller zu erzeugen und zu zerstören.

    Noch ein Beispiel, wo man um ein new nicht herumkommt.

    A* foo() {
      int a, b;
      cin >> a;
      cin >> b;
      return new A();
    }
    

    Natürlich könnte es auch so aussehen:

    A a;
    foo(&a);
    
    void foo(A* a) {
      cin >> a->a;
      cin >> a->b;
    }
    

    Aber wie sieht das aus? Sehr hässlich.

    Grüße 🙂

    Häh? Dein erstes Programm ist eine Designtodsünde! Niemals mit new allokierte Objekte aus Funktionen zurück geben! Damit verletzt du alles was krümelkacker über Objektzugehörigkeit erklärt hat und bekommst dicke Designprobleme!

    A foo()
    {
     A a;
     cin >> a; // Der Operator ist natürlich überladen
     return a; // RVO 
    }
    
    // ...
    
    A a = foo();
    

    Oder auch dein zweites Beispiel, aber in gut (aber schlechter als mein erstes Beispiel):

    void foo(A &a)
    {
     cin >> a;
    }
    
    // ...
    
    A a;
    foo(a);
    

    edit: Oh, viel zu langsam. 3x die gleiche Antwort vor mir. Immerhin hoffentlich überzeugend für den Threadersteller.



  • Michael E. schrieb:

    Aber auch sonst wäre es keine gute Idee, sich das Leben so viel schwerer mit Pointern zu machen, nur um ein paar Millisekunden - wenn überhaupt - einzusparen.

    Hier muss ich mich selbst korrigieren: Der angegebene Zeitwert ist utopisch hoch. Angenommen, das Kopieren der 3000 Floats (12KB) würde wirklich eine ganze Millisekunde dauern. Dann würde man nur 12MB pro Sekunde kopieren können, ein Wert, über den man bei schlechten USB2-Sticks reden kann, aber ganz sicher nicht bei Arbeitsspeicher. Willst du also wirklich das Pointergefrickel haben, um dann Einsparungen im Mikrosekundenbereich zu haben?



  • A* foo() 
    {
      int a, b;
      cin >> a;
      cin >> b;
      return new A();
    }
    

    Man kann gleich Hackers supi-dupi Variante nehmen, falls es wirklich allokiert werden muss (Basisklassenzeiger usw.): 🤡 🙂

    std::unique_ptr<A> foo()
    {
        return new A;//Klammern können weg bei nicht-skalaren Typen
    }
    

    Hat schon kk angedeutet.



  • Funktioniert so nicht, weil der Ctor von unique_ptr explicit ist.

    Generell wäre es nett, wenn du zumindest korrekten Code posten würdest...



  • nightrider schrieb:

    Es ist nur so, dass mindestens 50% der C++ Tutorials vorschlagen, den Zeiger nach dem delete auf 0 zu setzen. Aber ich glaubs dir und werds nicht mehr machen.

    Meiner Meinung nach kommt das auf den Fall an. Was definitiv sinnlos ist, ist das Setzen auf Null von 'nem Zeigerelement im Destruktor einer Klasse.

    nightrider schrieb:

    Meine Klasse speichert Koordinaten von 3D-Objekten. Sagen wir ein 3D-Objekt hat 1000 Koordinaten. Jede Koordinaten hat 3 floats. Das heißt in der Klasse sind 3000 floats gespeichert.
    Was ist jetzt schneller zu kopieren? Ein 32bit Zeiger oder 3000 floats? Um die Frage zu beantworten brauche ich keinen Profiler. 🙂

    Da fehlt mir leider etwas Kontext, um das hier vernünftig zu kommentieren. Jedenfalls muss man sich heutzutage -- in Zeiten von C++11 -- weniger Sorgen darüber machen, wie teuer das Kopieren ist, weil ggf gar nicht kopiert werden muss.

    nightrider schrieb:

    @krümelkacker: Ein sehr schöner Text. Du hast recht, in C++ muss man sich sicher stärker darüber Gedanken machen, wem was zugeordnet ist und wer was auch wieder löscht. Dass allokierte Speicher während der Programmlaufzeit verschoben werden kann und dadurch meine Pointer ungültig werden wusste ich gar nicht. Das schockiert mich jetzt.

    Ich sprach von std::vector. Natürlich muss das Ding, wenn im reservierten Speicherblock kein Platz mehr für ein neues Element ist und Du nochmal push_back aufrufst, neuer Speicher reserviert und der Kram "verschoben" werden.

    nightrider schrieb:

    Ich schreibt alle so, als würdet ihr new/delete so gut wie nie verwenden bzw. als schlecht empfinden.

    Also auf mich trifft das zu. Ich verwende es so gut wie gar nicht, weil ich es einfach selten verwenden muss. Und wenn du es verwendest, obwohl man es anders hätte besser machen können, dann empfinde ich das auch als schlecht, na klar.

    nightrider schrieb:

    Wie gesagt, wenn Rechenzeit/Speicher wertvoll ist, dann macht es wenig Sinn, Objekte ständig zu kopieren.

    Wieso redest du immer vom Kopieren?

    nightrider schrieb:

    Natürlich kann man ein Objekt am Stack anlegen und dann mit Zeigern darauf verweisen. Aber das ist auch nicht immer sinnvoll. (Wenn wer ein Beispiel sehen will wann das nicht sinnvoll ist, dann zeig ich gern eins 😉 )

    Das behauptet auch keiner, dass das immer sinnvoll sei.

    nightrider schrieb:

    Noch ein Beispiel, wo man um ein new nicht herumkommt.

    A* foo() {
      int a, b;
      cin >> a;
      cin >> b;
      return new A();
    }
    

    Sorry, das mit dem "um new nicht herumkommen" ergibt sich nicht aus dem Beispiel. Das hängt vor allem von A ab. Und seit C++11 kann A hier auch was (indirekt) dickes und fettes à la

    struct vec3f {
      float coeffs[3];
    };
    
    struct A
    {
      std::vector<vec3f> vertices;
      double dings;
      double bums;
    };
    

    sein, ohne dass man sich Sorgen um die Rückgabe "by value" machen muss, dank move semantics. Ich sage nicht, du kommst immer um new herum. Ich sage nur: Du kommst um delete herum und brauchst new wahrscheinlich viel seltener als du glaubst.

    nightrider schrieb:

    Natürlich könnte es auch so aussehen:

    A a;
    foo(&a);
    
    void foo(A* a) {
      cin >> a->a;
      cin >> a->b;
    }
    

    Aber wie sieht das aus? Sehr hässlich.

    Es hat zumindest ein Problem weniger: Bei deiner ersten Version erschließt sich einem nicht beim Angucken der Signatur, ob der Aufrufer für das Löschen des Objekts verantwortlich ist oder nicht. Du hättest das in dem Fall mit unique_ptr<A> als Return-Typ selbstdokumentierend machen können. Dann kann der Aufrufer auch nicht so viel falsch machen und dann wär die Funktion nicht mehr so "hässlich". 😉

    Wenn du die Funktion also etwas zu zurückgeben lassen willst, nimmste unique_ptr<A> oder einfach nur A. Letzteres ist dann sinnvoll, falls ein Objekt der Klasse A schnell von einem Speicherort zu einem anderen umziehen kann. Beachte: Das muss nicht unbedingt ein "Kopieren" sein! Es ist sogar zu erwarten, dass das in dem Fall noch flotter ist, als das A-Objekt extra im Freispeicher anzulegen und ein unique_ptr<A> zurückzugeben. Dynamische Speicherallozierung ist sicherlich zeitlich teurer als das Kopieren von ein paar Bytes oder das effiziente "Umziehen" eines Objekts von Speicherort A nach Speicherort B. In vielen Fällen erzeugt der Compiler ein Funktionslokales Objekt, welches zurückgegeben werden soll, sogar schon direkt im "Speicherort B", als dort, wo es der Aufrufer zurück bekommt. Dann ist nicht mal das "Umziehen" nötig. Diese Optimierung nennt sich RVO (return value optimization).

    Alternativ zur void-Funktion kannst du natürlich auch noch schreiben:

    A a;
    cin >> a;
    
    istream& operator>>(istream& in, A& a)
    {
      in >> a.a;
      in >> a.b;
      return in;
    }
    

    So verkehrt ist das gar nicht. 😉

    Cheers!
    kk



  • Kellerautomat schrieb:

    Funktioniert so nicht, weil der Ctor von unique_ptr explicit ist.

    Generell wäre es nett, wenn du zumindest korrekten Code posten würdest...

    Aber natürlich.
    Nett ist mein zweiter Vorname.

    std::unique_ptr<A> foo()
    {
        return std::unique_ptr<A>( new A );
    }
    

    Das wäre halt die Smart-Pointer Variante.
    Klar ist new schlecht; Aber das eigentlich schlechte ist ja manuelle Speicherverwaltung, die der SP hier ja ausradiert.



  • Jockelx schrieb:

    Oder so:

    Dein erstes Beispiel sollte wohl new A(a,b) zurückgeben.

    Ja genau, sorry.

    A a;
    foo(&a);
    
    void foo(A* a) {
      cin >> a->a;
      cin >> a->b;
    }
    

    Also ich sollte erklären, warum ich das hässlich finde. A müsste, damit der obige Code funktioniert so aussehen:

    class A {
    public:
     A() {a = irgendwas, b=irgendwas;}
     A(int x, int y) {
     }
     int a, b;
     vec<B> v;
    };
    

    Mein Problem dazu:
    Ich brauche einen default Konstruktor und muss in diesem a,b mit irgendeinen Blödsinn initialisieren. Und dann später mit getter/setter a,b initialisieren. Das geht um das Konstruktorprinzip herum. Ein Konstruktor ist ja da, um ein Objekt in einen gültigen Zustand zu bringen. Vielleicht noch wichtiger: Initialisierungsliste von Konstruktoren: Da brauche ich, soweit ich das verstanden habe, zwingend einen Konstruktor, der gültige Werte für die Initialisierungsliste bekommt. Wie soll das mit einem Default-Konstruktor gehen??

    Jockelx schrieb:

    Code:
    A a;
    foo(a);

    void foo(A& a) {
    cin >> a.a;
    cin >> a.b;
    }

    Und das sieht gut aus.

    Das würde wegen meines obigen Problems nicht funktionieren. 🙂

    Michael E. schrieb:

    na dann miss mal. Wann werden die Objekte im vector denn kopiert? Nur dann, wenn du den vector selbst kopierst (was so gut wie nie vorkommt) und wenn der vector neuen Speicher alloziert und den alten wegwirft.

    Das heißt bei foldendem:

    vec<int> v1, v2;
    v1->push_back(30);
    v2 = v1;
    

    Werden die Element im vector nicht kopiert? Wäre ja gar nicht schlecht. Ich habe jetzt eigentlich auch gar nicht einen vector gemeint, sondern ein dynamisch angelegtes array. Und ein Array mit 3000 oder 300000 Elemente zu kopieren dauert halt lange, hab ich mir gedacht. Aber ich glaub dir, wenn du sagst, dass das schnell genug geht. 🙂

    camper schrieb:

    Sinnlos ist diese Aussage, weil völlig unspezifisch. Ich kann auch überhaupt nicht erkennen, wo die Implikation hier plötzlich herkommt. Wir kopieren doch nicht zum Spaß oder weil zufällig Speicher da ist und Zeit verschwendet werden kann, sondern deshalb, weil der verwendete Algorithmus das Anlegen einer Kopie erfordert.

    Wenn ich eben meiner Klasse A von oben verwende, und schreibe

    A a, b;
    b.v.push_back(irgendwas);
    a.v = b.v;
    

    dann habe ich mir gedacht, dass auch der gesamte Vektor kopiert wird. Wie Michael E. meint geschieht das aber nicht.

    SeppJ schrieb:

    Wenn du einmal am Programmanfang die Objekte erzeugst, wird das weniger als 0.001 % der Gesamtlaufzeit ausmachen, dafür kostet dich die Indirektion bei jedem Zugriff. Auch sonst kann (gerade in c++11) die ganze Kopiererei ganz wunderbar wegoptimiert werden. Deine Objekte werden ja sicherlich nicht selber eine sizeof von 3000 floats haben, sondern einen Verweis auf 3000 floats auf dem Heap haben, die dann gemoved werden können, was ungefähr die Kosten einer Pointerkopie hat.

    Stimmt eigentlich, an die Kosten der Indirektion hab ich noch gar nicht gedacht.

    krümelkacker schrieb:

    Diese Optimierung nennt sich RVO (return value optimization)

    Werde ich mir anchaun 🙂


  • Mod

    nightrider schrieb:

    Jockelx schrieb:

    Oder so:

    Dein erstes Beispiel sollte wohl new A(a,b) zurückgeben.

    Ja genau, sorry.

    A a;
    foo(&a);
    
    void foo(A* a) {
      cin >> a->a;
      cin >> a->b;
    }
    

    Also ich sollte erklären, warum ich das hässlich finde. A müsste, damit der obige Code funktioniert so aussehen:

    class A {
    public:
     A() {a = irgendwas, b=irgendwas;}
     A(int x, int y) {
     }
     int a, b;
     vec<B> v;
    };
    

    Mein Problem dazu:
    Ich brauche einen default Konstruktor und muss in diesem a,b mit irgendeinen Blödsinn initialisieren. Und dann später mit getter/setter a,b initialisieren. Das geht um das Konstruktorprinzip herum. Ein Konstruktor ist ja da, um ein Objekt in einen gültigen Zustand zu bringen. Vielleicht noch wichtiger: Initialisierungsliste von Konstruktoren: Da brauche ich, soweit ich das verstanden habe, zwingend einen Konstruktor, der gültige Werte für die Initialisierungsliste bekommt. Wie soll das mit einem Default-Konstruktor gehen??

    A foo()
    {
     int argument1, argument2;
     cin >> argument1 >>> argument2;
     return A(argument1, argument2);
    }
    

    Wenn dir so viele erfahrene Leute sagen, dass du kein new brauchst und es ohne viel schöner ist, dann solltest du sich ernsthaft fragen, ob du schon genug weißt, um das besser zu beurteilen.

    Das heißt bei foldendem:

    vec<int> v1, v2;
    v1->push_back(30);
    v2 = v1;
    

    Werden die Element im vector nicht kopiert?

    Hier doch. Schließlich braucht v2 die gleichen Elemente und v1 existiert weiter.

    Wäre ja gar nicht schlecht. Ich habe jetzt eigentlich auch gar nicht einen vector gemeint, sondern ein dynamisch angelegtes array.

    Noch so etwas: new für Arrays brauchst du nie! Das ist ein starkes "nie". Nimm immer vector. Ist immer besser.

    Und ein Array mit 3000 oder 300000 Elemente zu kopieren dauert halt lange, hab ich mir gedacht. Aber ich glaub dir, wenn du sagst, dass das schnell genug geht. 🙂

    Zum zehnten Mal: Du hast wahrscheinlich gar keine Kopien, sondern Verschiebungen. Wenn dein Algorithmus tatsächlich Kopien erfordern sollte, dann brauchst du eine Kopie. Da kommst du auch nicht durch Pointerindirektion herum, schließlich müsstest du immer noch kopieren. Wenn du keine Kopie brauchst, dann mach auch keine! Die Sprache zwingt dich jedenfalls nicht dazu. (Wobei es zugegebenermaßen recht einfach ist, in C++ versehentlich eine ungewollte Kopie zu machen)

    camper schrieb:

    Sinnlos ist diese Aussage, weil völlig unspezifisch. Ich kann auch überhaupt nicht erkennen, wo die Implikation hier plötzlich herkommt. Wir kopieren doch nicht zum Spaß oder weil zufällig Speicher da ist und Zeit verschwendet werden kann, sondern deshalb, weil der verwendete Algorithmus das Anlegen einer Kopie erfordert.

    Wenn ich eben meiner Klasse A von oben verwende, und schreibe

    A a, b;
    b.v.push_back(irgendwas);
    a.v = b.v;
    

    dann habe ich mir gedacht, dass auch der gesamte Vektor kopiert wird. Wie Michael E. meint geschieht das aber nicht.

    Hier wird schon kopiert. Aber da greift wieder campers Argument über sinnloses Kopieren. Das Beispiel ist vermutlich viel zu kurz, aber diese Kopie hier ist jedenfalls zum Spaß und zufällig.


Anmelden zum Antworten