New und Delete



  • Hey Leute,
    ich hab mich hier mal neu angemeldet da ich keine Lust mehr habe das welche aus meiner Firma mich hier finden. Aber gut da diese nicht oft hier rein schauen: Enno war mein letzter Nick.

    Ich würde mich auch gleich gerne über etwas schlau machen. Und zwar geht es mir um die Benutzung von new und delete. Diese werden hier zu jeden Anlass benutzt. Ich empfinde das nicht so gut aber da ich hier gezwungen bin das zu machen, werde ich das hier machen.
    Nur als beispiel so was ist keine Seltenheit:

    blub = new bla[50];
    

    Auch bei klassen und alle wird dieses gerne gemacht. Der ein oder andere kennt das vielleicht aus meinem Code.
    Ich würde das gerne richtig lernen. Vielleicht könnte mir einer noch einmal die Schwierigkeiten und Problem dazu erläutern aber auch wo man es vielleicht Sinnvoll nutzt.
    Viel Dank 🙂



  • Das Problem von manueller Speicherverwaltung ist, dass du hinter dir wieder aufräumen musst:

    int * i = new int(5378);
    
    someFunction();
    // ... hier passiert was
    
    delete i;
    

    Das sieht an sich nicht schwer aus, ist es eigentlich auch nicht, aber man vergisst das delete vielleicht mal.

    Oder noch schlimmer, was passiert wenn eine Exception in someFunction() geworfen wird?
    Tja, dann ist dein Speicher weg für die restliche Lebenszeit deines Programms. und auch das ist eigentlich nicht weiter tragisch, da sich das Betriebssystem darum kümmert, wenn dein Programm endet. Aber wenn dein Programm sowas öfters macht und vielleicht viel Speicher braucht, dann wird das früher oder später in die Hose gehen. Und solche Fehler findet man oft nur schwer.

    Auf der anderen Seite musst du sehr gut aufpassen, dass du die speichernden zeiger nicht nur tauscht anstatt tief zu kopieren.

    Und solche Semantiken werden alle durch Smart-Pointer oder (STL-)Container behoben. Z.B. ein vector kopiert sich inklusive Inhalt komplett anstatt nur den Zeiger zu tauschen (obwohl das manchmal sogar gewollt sein könnte).

    Der Hauptgrund keine rohen Zeiger zu nutzen ist halt, dass du nie mehr vergisst deinen Speicher zu löschen und du durch diese Automatisierung sogar Exceptionsicherheit kostenlos dazu bekommst.

    Da gibts einige gute Artikel von Toni (Shade of Mine) hier im Forum



  • Das ist zu umfangreich fuer einen Post. Gedankenspiel, angenommen dir steht alles aus C++ zur Verfuegung, nur new und delete nicht*. Wie wuerdest du es dann machen?

    Beispielsweise so:

    std::vector<bla> blub(50);
    

    * Natuerlich koennen Elemente der Standardbibliothek weiterhin new und delete benutzen. Nur du als Nutzer dieser hast keinen Zugriff.


  • Mod

    Fuchs aus dem Wald schrieb:

    Ich würde das gerne richtig lernen.

    Das ist ganz einfach: Nutze es nicht!

    Übersetzungstabelle:
    Kleine, statische Arrays

    irgendwas *foo = new irgendwas[feste_zahl];  // mit sizeof(irgendwas)*feste_zahl < 100 000
    

    wird zu

    irgendwas foo[feste_zahl];
    // oder besser noch
    std::array<irgendwas, feste_zahl> foo;  // In älteren Compilern std::tr1::array
    

    Riesige konstante Arrays

    irgendwas *foo = new irgendwas[feste_zahl];  // mit sizeof(irgendwas)*feste_zahl > 100 000
    

    wird zu

    std::vector<irgendwas> foo(feste_zahl);
    

    Dynamische Arrays

    irgendwas *foo = new irgendwas[variable_zahl];
    

    wird zu

    std::vector<irgendwas> foo(variable_zahl);
    

    Nicht-Arrays

    {  // Scopebegin
       // ...
       irgendwas *foo = new irgendwas;
       // ...
       delete foo;
       // ...
    }  // Scopeende
    

    wird zu

    {  // Scopebegin
       // ...
       irgendwas foo;
       // ...
    }  // Scopeende
    

    Weiteres Pointergefrickel

    irgendwas *foo = /* egal was */ ;
    

    wird zu

    std::unique_ptr<irgendwas> foo;
    // oder
    std::shared_ptr<irgendwas> foo;
    // oder
    std::weak_ptr<irgendwas> foo;
    // oder
    irgendwas *foo;  // (ja, manchmal sind auch rohe Pointer richtig)
    

    Je nach Bedarf.



  • Danke schon mal das klärt schon mal gut auf.

    Das Argument was hier gebracht wird ist einfach das es schneller ist. Für mich klingt das aber hier in diesem, ich sag mal großem Projekt, das da echt hart Fehler drin sein können. Vor allem das ich mir das alles selber bei bringen muss. So richtig "gelehrt" werde ich nicht.
    Aber gut es ist ein großes Projekt und da muss alles schnell und fix sein.

    Edit:
    @SeppJ

    Das ist ganz einfach: Nutze es nicht!

    Ja in privaten Projekten mach ich das auch. 😉 Aber hier muss ich ja ... 😕


  • Mod

    Es ist langsamer oder gleich schnell, niemals schneller. Besonders gegen automatische Speicherverwaltung (in meiner Übersetzungstabelle "Kleine, statische Arrays" und "Nicht-Arrays") ist der Geschwindigkeitsverlust nicht mehr lustig.

    Ja in privaten Projekten mach ich das auch. 😉 Aber hier muss ich ja ... 😕

    Aber was möchtest du dann wissen? Die richtige Benutzung ist, es nicht zu benutzen. Wenn du es benutzen musst, dann kannst du es nicht richtig benutzen.



  • SeppJ schrieb:

    Es ist langsamer oder gleich schnell, niemals schneller. Besonders gegen automatische Speicherverwaltung (in meiner Übersetzungstabelle "Kleine, statische Arrays" und "Nicht-Arrays") ist der Geschwindigkeitsverlust nicht mehr lustig.

    D.h. der erzählt mir hier scheiße?? 😮
    Oh man ...
    Also noch mal für mich. New und delete ist sogar langsamer? Ahhhhhh 😡



  • Fuchs aus dem Wald schrieb:

    Das Argument was hier gebracht wird ist einfach das es schneller ist.

    Wäre ein Argument, wenns stimmen würde. Tut es aber nicht.

    {
      // ... zum ersten
      std::unique_ptr<irgendwas> u(new irgendwas);
      // ... zum zweiten
    }
    

    wird von jedem Compiler geinlined zu

    {
      // ... zum ersten
      irgendwas *u;
      try {
        u = new irgendwas;
        // ... zum zweiten
      } catch (...) {
        delete u;
        throw;
      }
      delete u;
    }
    

    Nein warte, es ist sogar schneller weil Stack Unwinding und Exceptions speziell optimiert werden kann.

    Das einzige, was bei SeppJ eventuell langsamer ist, ist std::vector . Aber auch hier gibt es Abhilfe:

    irgendwas *foo = new irgendwas[variable_zahl]; // 3 Pointer Verwaltungsdaten
    

    wird zu

    std::unique_ptr<irgendwas[]> foo(new irgendwas[variable_zahl]); // 1 Pointer Verwaltungsdaten
    

    Es gab bei mir aber noch einen Grund, nicht vector zu nehmen.



  • Ahhhhhh schrieb:

    Das einzige, was bei SeppJ eventuell langsamer ist, ist std::vector . Aber auch hier gibt es Abhilfe:

    irgendwas *foo = new irgendwas[variable_zahl]; // 3 Pointer Verwaltungsdaten
    

    wird zu

    std::unique_ptr<irgendwas[]> foo(new irgendwas[variable_zahl]); // 1 Pointer Verwaltungsdaten
    

    Es gab bei mir aber noch einen Grund, nicht vector zu nehmen.

    Das versteh ich nicht. Da ist ja immer noch ein new drin?


  • Mod

    Das einzige, was bei SeppJ eventuell langsamer ist, ist std::vector

    Nun komm, 3 Pointer vs. 1 Pointer + Größe: Das ist so was von Pillepalle.

    Dafür kann man den vector in O(1) wachsen lassen, was bei new nur in O(N) geht. Wenn man es natürlich nicht benötigt, dann kann der vector vereinzelt(!) im Programm eine Subtraktion benötigen, wo new+size nur einen Zugriff benötigt. Also braucht das Gesamtprogramm eventuell 10-20 Prozessortakte mehr.

    Dein Vorschlag ist zwar technisch richtig, würde ich aber trotzdem nicht machen. Auf diesem Pfad stößt man nämlich bald auf die Performancevoodookulte, deren Anhänger Enno in seiner Firma angetroffen hat.



  • Vor allem das ich mir das alles selber bei bringen muss. So richtig "gelehrt" werde ich nicht.

    Und den meisten hier hats auch niemand beigebracht. Das Wissen musst du dir selbst aneignen, das Internet hilft und Artikel gibt es genug.



  • Die Geschwindigkeit von den Speicherallokationen ist überhaupt nicht maßgeblich. Das, was da langsam wäre, wäre die Speicherallokation selbst. Das ist aber in beiden Fällen gegeben, somit spielt das keine Rolle. Wenn new wirklich performancekritisch eingesetzt wird, sollte man ohnehin einen eigenen Allocator schreiben und der ist mit unique_ptr wieder gut kompatibel.



  • SeppJ schrieb:

    Nun komm, 3 Pointer vs. 1 Pointer + Größe: Das ist so was von Pillepalle.

    Ist so. Aber man kann Ennos Kollegen ohne schlechtes Gewissen sagen, dass RAII nie langsamer ist.

    SeppJ schrieb:

    Dafür kann man den vector in O(1) wachsen lassen, was bei new nur in O(N) geht.

    Viel kann natürlich auf genau auf das Problem zugeschnittene Container herausgeholt werden (ein reserve kann den Speicher bis um Faktor 2 schrumpfen). Oder durch eigene Allocators.



  • @SeppJ:
    Bitte neuen Namen nehmen. 😉
    Ja DEN hab ich hier gefunden. Ist eine Person. 😃

    knivil schrieb:

    Vor allem das ich mir das alles selber bei bringen muss. So richtig "gelehrt" werde ich nicht.

    Und den meisten hier hats auch niemand beigebracht. Das Wissen musst du dir selbst aneignen, das Internet hilft und Artikel gibt es genug.

    Da hast aber wunderbar nur das gelesen was du wolltest. Ich mach das auch. UUHH! Aber ich mache eine Ausbildung da könnte man meinen man wird in gewissermaßen "gelehrt" und nicht nur gesagt: geht so mach so!

    Eisflamme schrieb:

    Die Geschwindigkeit von den Speicherallokationen ist überhaupt nicht maßgeblich. Das, was da langsam wäre, wäre die Speicherallokation selbst. Das ist aber in beiden Fällen gegeben, somit spielt das keine Rolle. Wenn new wirklich performancekritisch eingesetzt wird, sollte man ohnehin einen eigenen Allocator schreiben und der ist mit unique_ptr wieder gut kompatibel.

    Danke das klärt mich weiter auf.

    Allgemein danke schon mal das ihr mich noch mal genau aufklärt. Muss ich nun hier zwar durch aber ich hab kein bock mich für mein späteres Berufsleben zu versauen.



  • Fuchs aus dem Wald schrieb:

    SeppJ schrieb:

    Es ist langsamer oder gleich schnell, niemals schneller. Besonders gegen automatische Speicherverwaltung (in meiner Übersetzungstabelle "Kleine, statische Arrays" und "Nicht-Arrays") ist der Geschwindigkeitsverlust nicht mehr lustig.

    D.h. der erzählt mir hier scheiße?? 😮
    Oh man ...
    Also noch mal für mich. New und delete ist sogar langsamer? Ahhhhhh 😡

    Schreibe eine eigene einfach(!) verkettet Liste mit eigenem new und delete und eigener Speicherverwaltung (lässt sich auch C-like hübsch machen)). Und dann lasse sie gegen die doppelt(!) verkette std::list<> aus dem C++-Standard antreten. Letztere ist im Nachteil, da sie bei jeder Element-Operation mehr Zeiger aktualisieren muss, als die einfach verkettete Liste.

    Mache bitte einen fairen Benschmark mit vielen (möglichst kleinen) Elementen rein und raus aus der Liste. Und lege auch viele solcher Listen an und lösche sie wieder ... alles was in 'großen' Programmen so passiert.

    Dann wirst Du Dich wundern, um wie viel schneller die std::list<> das gegenüber Deiner selbst gebastelten erledigt. Und ich rede nicht von 5 oder 10% - die std::list<> ist richtig schneller - und die Ursache liegt in der besseren Speicherverwaltung!
    Probiere es aus - nachdem ich das gemacht hatte, musste ich mich auch erst mal setzen!
    (Ausprobiert mit MS Visual C++ und Dinkumware STL)


  • Mod

    Werner Salomon schrieb:

    und die Ursache liegt in der besseren Speicherverwaltung!

    Nanu? Kannst du konkreter werden? Ich hätte eigentlich nicht erwartet, dass man bei einer list viel durch die Implementierung rausholen kann. Arbeitet die deque-artig?



  • Werner Salomon schrieb:

    Dann wirst Du Dich wundern, um wie viel schneller die std::list<> das gegenüber Deiner selbst gebastelten erledigt. Und ich rede nicht von 5 oder 10% - die std::list<> ist richtig schneller - und die Ursache liegt in der besseren Speicherverwaltung!

    Kann ich nicht bestätigen (GCC/libstdc++).

    Meine List: 0.75s auf ideone
    std::list: 0.88s auf ideone



  • Kein unique_ptr? Ich muss doch bitten! 🤡



  • cooky451 schrieb:

    Kein unique_ptr? Ich muss doch bitten! 🤡

    Ich gebe zu, die PODs vom Betriebssystem aufräumen zu lassen war nicht so freundlich.

    Meine List ohne Leaks und UB: 0.75s auf ideone

    Den unique_ptr innerhalb von node einzusetzen kostet leider ein paar Prozessortakte mehr.



  • wtf, ne, so war das jetzt nicht gemeint. Eine node sollte aus T + unique_ptr<node> bestehen, die liste sollte nur nen unique_ptr<node> halten. Damit werden alle Einfüge/Löschoperationen auch nur noch zu Zuweisungen zwischen unique_ptr. Ist die Frage, ob das langsamer ist, denke aber nicht.


Log in to reply