Wann new verwenden und wann nicht?



  • Ich habe zwei Möglichkeiten mit der Klasse Test zu arbeiten:

    class Test
    {
    public:
    	void DoSomething()
    	{
    		cout << "Doing something ..." << endl;
    	}
    };
    
    // Möglichkeit 1
    Test * test1 = new Test();
    test1->DoSomething();
    
    // Möglichkeit 2
    Test test2;
    test2.DoSomething();
    

    Beides scheint ziemlich gleich zu schein. Hat eine der beiden Möglichkeiten Vorteile der anderen Möglichkeit gegenüber?



  • Wenn du merkst, dass du es brauchst.



  • Das Zweite ist einfacher als das Erste. Bevorzuge immer den einfacheren Code, sofern dich nichts daran hindert.

    Bei deiner Möglichkeit 1 fehlt außerdem noch das delete irgendwo.


  • Mod

    Oftmals, aber nicht zwingend, bei:

    • Polymorphie
    • ungwöhnlicher Objektlebenszeit
    • Hausaufgaben, bei denen man die Standardbibliothek nachprogrammieren soll 🙄 , aber noch nicht so weit ist, einen abstrakten Allokator zu benutzen 🙄 🙄

    Nicht bei:

    • dynamischen Arrays
    • weil man von Java kommt und das so gewohnt ist
    • weil man einen Pointer braucht


  • Wird bei der 2. Möglichkeit das Objekt nicht auf dem Stack erstellt? Kann es bei zu vielen Objekten nicht zu einem Stackoverflow kommen?


  • Mod

    Hmm, mit Frage schrieb:

    Wird bei der 2. Möglichkeit das Objekt nicht auf dem Stack erstellt? Kann es bei zu vielen Objekten nicht zu einem Stackoverflow kommen?

    Ja, bei ein paar Millionen.



  • Hmm, mit Frage schrieb:

    Wird bei der 2. Möglichkeit das Objekt nicht auf dem Stack erstellt? Kann es bei zu vielen Objekten nicht zu einem Stackoverflow kommen?

    Wenn du das ohne Arrays schaffst, kriegst du nen Keks. 😉



  • cooky451 schrieb:

    Wenn du das ohne Arrays schaffst, kriegst du nen Keks. 😉

    void foo()
    {
        foo();
    }
    
    int main()
    {
        foo();
    }
    

  • Mod

    314159265358979 schrieb:

    cooky451 schrieb:

    Wenn du das ohne Arrays schaffst, kriegst du nen Keks. 😉

    void foo()
    {
        foo();
    }
    
    int main()
    {
        foo();
    }
    
    $> g++ test.cc [b]-O3[/b]
    $> ./a.out
    

    Nach ein paar Minuten:

    $> top
    ...
      PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND            
    24389 seppj     20   0  4024  324  248 R  100  [b]0.0 [/b]  2:08.02 a.out 
    ...
    

    Hervorhebung durch mich.



  • Dann schubs ihm halt noch einen Parameter dazu 😃


  • Mod

    314159265358979 schrieb:

    Dann schubs ihm halt noch einen Parameter dazu 😃

    void foo(int)
    {
        foo(3);
    }
    
    int main()
    {
        foo(3);
    }
    

    Immer noch nix, nur Endlosschleife. Du musst dich schon anstrengen für deinen Keks. Tipp: Der GCC ist recht gut im Optimieren von tail recursions, selbst wenn die Umformung nicht so offensichtlich ist.



  • std::string foo(int i)
    {
        std::string s(i);
        return s + foo(i + 1);
    }
    
    int main()
    {
        foo(0);
    }
    

    So vielleicht?


  • Mod

    314159265358979 schrieb:

    std::string foo(int i)
    {
        std::string s(i);
        return s + foo(i + 1);
    }
    
    int main()
    {
        foo(0);
    }
    

    So vielleicht?

    Ich habe Zeile 3 mal zu std::string s(i, ' '); gemacht, damit es compilert.

    Mal schaun....

    %&&îäÿûÿ5ÿ®®%®#@&@@&!%%Éÿ®ÎÊÂÕÐÞYREE$T% #T%ÉÝÉ%ÝT$

    ...Arghhhh!!!

    Das war Absicht, oder?

    Ich hätte mal genauer schauen sollen, bevor ich das starte.

    Jedenfalls immer noch kein Stack Overflow. Dafür aber den ganzen Heap vollgemüllt, und den Rechner in eine minutenlange Auslagerungsorgie geschickt, bis ich den Prozess abschießen konnte. Und danach nochmal ein paar Minuten zurücklagern.



  • 😃

    Sorry, das war wirklich nicht meine Absicht. Ich hätte damit gerechnet, dass der Stack eher eingeht, als der Heap. Naja, muss ich auch mal eben testen, welche Auswirkung das so auf mein System hat. 😃



  • So, hatte eigentlich selbst bei fast 3 GB am Swap kein Problem, weiterzuarbeiten. Musik lief auch weiter. OS X ist halt einfach super 😃

    Edit: Läuft sogar besser, als vorher 😃


  • Mod

    314159265358979 schrieb:

    😃

    Sorry, das war wirklich nicht meine Absicht. Ich hätte damit gerechnet, dass der Stack eher eingeht, als der Heap. Naja, muss ich auch mal eben testen, welche Auswirkung das so auf mein System hat. 😃

    Ich habe es wie gesagt nicht bis zu bitteren Ende getrieben, sondern sobald ich es merkte abgebrochen. Ich kann ja mal abschätzen:
    sizeof(string) ist 8 bei mir. Stackgröße ist 163840 kB. Ich mag jetzt nicht in den Assemblercode gucken, was der int macht, aber sagen wir mal nochmal 4 Byte pro Call. Das sind knapp 14 Millionen mögliche Calls. Für jeden Call bekommen wir einen String, der 1 Zeichen länger ist als der vorherige, je nachdem wie genau es läuft bekommen wir auch 2 Strings dieser Länge, aber dafür auch 2 Strings auf dem Stack, das hebt sich ungefähr weg. Jedenfalls sind das nach Gauß rund Calltiefe^2/2 Bytes auf dem Heap. Plus noch ein paar Byte Verwaltungsdaten, die ich mal vernachlässige. Mein Heimrechner ist nicht so üppig ausgestattet, insgesamt gut 13 GB stehen zur Verfügung. Das heißt, nach ein paar hundertausend Calls, geht mir der Heap aus und das Programm würde vermutlich mit einer passenden Exception enden, anstatt mit Stackoverflow. Und es ist auch noch gewaltig Platz nach oben, man bräuchte also wirklich ungewöhnlich viel Arbeitsspeicher um den Stack mit diesem Programm zu knacken.

    314159265358979 schrieb:

    So, hatte eigentlich selbst bei fast 3 GB am Swap kein Problem, weiterzuarbeiten. Musik lief auch weiter. OS X ist halt einfach super 😃

    Bei mir lief auch alles weiter, außer einen kleinen Videoaussetzer als er anfing auszulagern, wodurch ich überhaupt erst das Problem bemerkte. Ärgerlich war, dass das Programm selbst nur sehr träge auf das CTRL+C reagierte (beziehungsweise: Es hat sicherlich sehr prompt reagiert, aber die Millionen Anfragen nach mehr RAM waren schon abgeschickt und wurden noch verarbeitet). Und der Scheduler hat mir dann anscheinend den Flashplayer abgeschossen, als der RAM am Ende war 😡 .



  • Pi: Alle von dir geposteten Programme haben Undefined Behaviour:

    N3290 §1.10.24 schrieb:

    The implementation may assume that any thread will eventually do one of the following:
    — terminate,
    — make a call to a library I/O function,
    — access or modify a volatile object, or
    — perform a synchronization operation or an atomic operation.
    [Note: This is intended to allow compiler transformations such as removal of empty loops, even when termination cannot be proven. — end note]



  • Die Sache mit dem Stack und Heap interessiert mich mehr.

    In .NET (C#) beispielsweise ist es so, dass alle Referenztypen (somit auch Instanzen von Klassen) auf dem Heap angelegt werden und Wertetypen werden auf dem Stack angelegt werden (aus Performancegründen). Die klare Trennung existiert in C++ nicht; ich bin also in der Lage frei zu entscheiden, ob meine Instanz auf dem Stack oder Heap angelegt werden soll. Mal angesehen von der "Schönheit" des Codes – wann weiß ich wann ich Möglichkeit 1 oder Möglichkeit 2 (s. Codebeispiel auf der ersten Seite) bevorzugen soll?



  • inc7 schrieb:

    wann weiß ich wann ich Möglichkeit 1 oder Möglichkeit 2 (s. Codebeispiel auf der ersten Seite) bevorzugen soll?

    Generell ist der Stack vorzuziehen. Grund: die Allokation auf dem Heap kostet Performance, das Speichermanagement muss außerdem selbst gehandelt werden (auch wenn Smartpointer das erleichtern), weil C++ keinen GC hat. Zugriffe über (smart-)Pointer auf Heap-Objekte dauern auch länger als auf Stackobjekte.
    ABER: ein Stack-Objekt hat nur Scope-Lebenszeit, soll heißen, wenn die Funktion oder der Block verlassen wird, wird das Objekt zerstört. Solltest du also Objekte erzeugen, die länger leben sollen als die aktuelle Funktion, kommst du am Heap nicht vorbei.



  • In C# können auch Wertetypen auf dem Heap sein, aber das ist ein Detail. Um die Lage in C++ beurteilen zu können, musst die Frage nach der Lifetime der Objekte stellen. Wenn du mit auto-Semantik leben kannst, dann solltest du sie verwenden. Wenn nicht, kannst du anfangen an new zu denken 😉


Anmelden zum Antworten