array mit Zahlen füttern und sortieren



  • also wenn ich bspw. prison architect spiele, verlangsamt sich das spiel ab einer gewissen größe sehr stark. wenn ich dann speichere und neu lade, läuft es wieder flüssig. vermutung: man knallt alles in einen container und gibt da auch nichts frei.



  • @It0101

    Welcher Container das ist, lässt sich so ermitteln: https://stackoverflow.com/questions/471432/in-which-scenario-do-i-use-a-particular-stl-container

    Wobei das Sheet, wie in den Kommentaren bei SO erwähnt, heute nicht mehr so richtig passt. std::vector ist meistens ein guter Anfang und oft vorzuziehen; std::list dagegen ist in den wenigsten Fällen von Vorteil.

    // edit:
    Und für Neulinge: std::vector, std::vector, std::vector



  • @Wade1234 sagte in array mit Zahlen füttern und sortieren:

    vermutung: man knallt alles in einen container und gibt da auch nichts frei.

    Da es ja keine andere Möglichkeit gibt muss es so sein.

    Ach, Moment - könnte natürlich auch so jemand wie du gewesen sein, der Speicher anfordert aber nicht wieder freigibt.



  • @It0101 sagte in array mit Zahlen füttern und sortieren:

    weil du vergessen könntest ihn wieder freizugeben.

    und selbst das wäre ja kein wirkliches Argument, weil Du, @Wade1234, natürlich hoch und heilig sofort versprechen wirst, soetwas nie nicht zu vergessen. Davon will ich auch gerne ausgehen. Aber dann kommt eine phöse Exception dahergeflogen und Du hast nur einen rohen Pointer und guckst in die Röhre ... ätsch.

    RAII/RDID ist Dein Freund. Also für Pointer std::unique_ptr<> und std::shared_ptr<>, für Ressourcen die nicht mit delete oder delete[] freigegeben werden können eben mit custom-deleter. Sonst die Container der Standardlibrary. std::array<> wenn die Größe zur Compiletime feststeht, std::vector<> wenn nicht und es ein zusammenhängender Speicherbereich sein soll, std::deque<> wenn das nicht sein muss. Wenn Du einen anderen Container brauchst, dann weißt Du es.



  • @manni66
    also ich habe gelernt, dass zu jedem malloc immer ein free und du jedem new ein delete gehört und dass ich mir gleich nach der feststellung, dass speicher angefordert werden soll, gedanken machen muss, wo und wann der speicher freigegeben werden muss, damit sowas nicht passiert.

    also was ich sagen wollte: scheinbar passiert das mit den containern auch.



  • @Wade1234 sagte in array mit Zahlen füttern und sortieren:

    @manni66
    also ich habe gelernt, dass zu jedem malloc immer ein free und du jedem new ein delete gehört und dass ich mir gleich nach der feststellung, dass speicher angefordert werden soll, gedanken machen muss, wo und wann der speicher freigegeben werden muss, damit sowas nicht passiert.

    Was du nicht gelernt hast: man erwischt trotzdem nich alle Fälle. Immer.

    also was ich sagen wollte: scheinbar passiert das mit den containern auch.

    Woher du das auch immer wissen willst.



  • @Wade1234 sagte in array mit Zahlen füttern und sortieren:

    und dass ich mir gleich nach der feststellung, dass speicher angefordert werden soll, gedanken machen muss, wo und wann der speicher freigegeben werden muss, damit sowas nicht passiert.

    Nein, Himmeldonnerwetter nochmal. RAII + RDID !!

    @Wade1234 sagte in array mit Zahlen füttern und sortieren:

    also was ich sagen wollte: scheinbar passiert das mit den containern auch.

    Was passiert da "auch"?



  • Was @manni66 mit

    @manni66 sagte in array mit Zahlen füttern und sortieren:

    Was du nicht gelernt hast: man erwischt trotzdem nich alle Fälle. Immer.

    zB meint:

    #include <cstddef>
    #include <algorithm>
    
    class foo
    {
        std::size_t num_bars;
        int *bar;
        std::size_t num_quxs;
        int *qux;
    
    public:
        foo(std::size_t num_bars, std::size_t num_quxs)
        : num_bars { num_bars          },
          bar      { new int[num_bars] },
          num_quxs { num_quxs          },
          qux      { new int[num_quxs] }
        {}
    
        foo(foo const &other)
        : num_bars { other.num_bars    },
          bar      { new int[num_bars] },
          num_quxs { other.num_quxs    },
          qux      { new int[num_quxs] }    
        {
            std::copy(other.bar, other.bar + num_bars, bar);
            std::copy(other.qux, other.qux + num_quxs, qux);
        }
    
        foo& operator=(foo other)
        {
            std::swap(num_bars, other.num_bars);
            std::swap(bar, other.bar);
            std::swap(num_quxs, other.num_quxs);
            std::swap(qux, other.qux);
            return *this;
        }
    
        ~foo()
        {
            delete[] bar;
            delete[] qux;
        }
    };
    

    @Wade1234 Passt so, oder? Ich meine, Constructor besorgt Speicher, Copy-Constructor macht schön eine tiefe Kopie (also nicht nur die Pointerwerte - dann gäbe es ja wenn die Destruktoren von Origial und Kopie laufen bei einem von beiden ein delete[] auf ein bereits freigegebenes Pointervalue ~> Undefined Behaviour.), Assignment-Operator macht brav Copy-and-Swap und der Destruktor räumt das ganze wieder auf. Alles schön. (?)

    Oder einfacher, sowas:

    #include <cstddef>
    #include <limits>
    #include <stdexcept>
    #include <iostream>
    
    void read_pigs(int *first, int *last)
    {
    	for (auto *i{ first }; i != last; ++i) {
    		if (!(std::cin >> *i))
    			throw std::runtime_error{ "Eingabefehler!" };
    	}
    }
    
    void print_pigs(int const *first, int const *last)
    {
    	for (auto *i{ first }; i != last; ++i)
    		std::cout << *i << ' ';
    	std::cout.put('\n');
    }
    
    int main()
    {
    	std::size_t count;
    
    	while (std::cout << "Wie viele Schweiderl?\n", !(std::cin >> count)) {
    		std::cerr << "Eingabefehler.\n\n";
    		std::cin.clear();
    		std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    	}
    
    	auto *pigs{ new int[count] };
    	read_pigs(pigs, pigs + count);
    
    	std::cout << "\nEingabe war:\n";
    	print_pigs(pigs, pigs + count);
    
    	delete[] pigs;
    }
    

    Am Anfang von main() sobald die Größe feststeht Speicher besorgen und am Ende wieder freigeben. Easy. (?)



  • @Wade1234 sagte in array mit Zahlen füttern und sortieren:

    also wenn ich bspw. prison architect spiele, verlangsamt sich das spiel ab einer gewissen größe sehr stark. wenn ich dann speichere und neu lade, läuft es wieder flüssig. vermutung: man knallt alles in einen container und gibt da auch nichts frei.

    Für Performance-Probleme gibt es tausende Gründe. Eventuell ist auch ein Container sehr groß und wird öfter kopiert.
    STL-Container ersetzen nicht die Gehirntätigkeit, sie reduzieren nur die Fehlerquote.



  • @It0101
    ja das meinte ich ja. raii bzw. das konzept dahinter (die bedeutung der bezeichnung musste ich erst einmal nachlesen) war mir ja an sich bekannt. aber wenn ich etwas in einen container stecke, muss ich das ja irgendwann auch mal wieder daraus entfernen und das kriegen die leute scheinbar nicht hin. im endeffekt ist das doch das gleiche, wie das vergessen von delete, oder nicht?



  • @Wade1234 sagte in array mit Zahlen füttern und sortieren:

    @It0101
    ja das meinte ich ja. raii bzw. das konzept dahinter (die bedeutung der bezeichnung musste ich erst einmal nachlesen) war mir ja an sich bekannt. aber wenn ich etwas in einen container stecke, muss ich das ja irgendwann auch mal wieder daraus entfernen und das kriegen die leute scheinbar nicht hin. im endeffekt ist das doch das gleiche, wie das vergessen von delete, oder nicht?

    Nein. Wenn du das Delete vergessen hast, hast du erfolgreich ein Memory-Leak produziert, während ein "übervoller" Container sich spätestens beim Beenden der Anwendung wieder aufräumt. D.h. es wurde technisch sauber umgesetzt, aber das Konzept passt in dem Fall nicht ( zu viele Kopieroperationen, ineffiziente Datenhaltung, etc. ), aber ein vergessenes Delete ist einfach schlampige Programmierung. An der falschen Stelle führt ein vergessenes Delete früher oder später zu std::bad_alloc.

    Der Idealfall ist, dass du über dein Konzept/Datenmodell ( Datentransport, Datenhaltung ) intensiv nachgedacht hast und dann durch Verwendung von STL-Containern auch noch sauber umgesetzt hast.



  • @Wade1234 Hast Du bei meinen beiden Beispielen eigentlich gesehen, warum "händisches" new und delete so wie in den Beispielen Käse ist und warum man wenn dann Smartpointer statt roher (besitzender) Zeiger nimmt? Irgendwie empfinde ich es schon ein wenig seltsam sich Mühe zu geben Dir etwas zu erklären und 0 Feedback zu bekommen.



  • @Wade1234
    Ich glaube du hast RAII noch nicht ganz verstanden. Ein kleines etwas anderes Beispiel:

    #include <iostream>
    #include <chrono>
    #include <thread>
    
    
    class MyLogger  // Rudimentäre auf Sichtbarkeit getrimmte Klasse zum Logging von Funktions- bzw. Blockaufrufen.
    {
    private:
        std::string mMessage;
        std::chrono::system_clock::time_point mStartPoint;  // Zeitpunkt an dem die Klasse angelegt wurde
    
    
    public:
        MyLogger(const std::string& Message)
        {
            mMessage = Message;
            mStartPoint = std::chrono::system_clock::now();
            std::clog << mMessage << " starts!\n";
        }
    
        MyLogger(const MyLogger&) = delete;               // Kopierkonstruktor wollen wir nicht
        MyLogger& operator=(const MyLogger&) = delete;    // Kopieroperator wollen wir auch nicht
    
        ~MyLogger()
        {
            std::chrono::system_clock::time_point EndPoint = std::chrono::system_clock::now();
            long long Diff = std::chrono::duration_cast<std::chrono::milliseconds>(EndPoint - mStartPoint).count();
    
            std::clog << mMessage << " ends after " << Diff << " ms!\n";
        }
    
    };
    
    
    
    int main()
    {
        MyLogger m("main()");
        {
            MyLogger m1("Block 1");
            {
                MyLogger m2("Block 2");
                {
                    MyLogger m3("Block 3");
                    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
                    // Sehr viel Code mit vielen return Anweisungen
    
    
                }
                std::this_thread::sleep_for(std::chrono::milliseconds(1000));
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        }
        return 0;
    }
    

    Die Klasse MyLogger loggt auf der Console wann eine (DLL) Funktion oder Block aufgerufen wurde und misst wie lange die Funktion oder Block benötigt.

    Nun stelle dir mal vor, du nutzt die Klasse in einer größeren Berechnungsfunktion mit hunderten von Codezeilen. Dummerweise sind in dieser Funktion sehr viele return Anweisungen. Dann müsstest du pro return Anweisung 3 Codezeilen einfügen, sicherstellen das du alle return Anweisungen gefunden hast und sicherstellen dass das auch in Zukunft passiert.

    Ziemlich viel fehleranfällige Arbeit.

    PS:
    Der Code funktioniert auch dann wenn der Berechnungscode Exceptions wirft.

    PPS:
    Habe Code entsprechend DocShoe's Wünschen angepasst.



  • Wenn du die Parameter des Kopierkonstruktors und Assignment-Operators noch const machst und clog statt printf benutzt fände ich das ein erstklassiges Beispiel.



  • @Swordfish
    naja die container machen das intern doch genauso, oder nicht? also abgesehen von "arbeitsersparnis", weil das schon jemand anderes gemacht hat, fällt mir da jetzt nichts auf.

    @Quiche-Lorraine sagte in array mit Zahlen füttern und sortieren:

    @Wade1234
    Ich glaube du hast RAII noch nicht ganz verstanden. Ein kleines etwas anderes Beispiel:

    naja sobald die jeweiligen anweisungsblöcke verlassen werden, verlieren m3, m2 und m1 ihre gültigkeit, wodurch der darin vorhandene string auch ungültig wird, was den aufruf des destruktors von string und dadurch die freigabe der ressourcen zur folge hat.



  • @Wade1234 sagte in array mit Zahlen füttern und sortieren:

    naja die container machen das intern doch genauso, oder nicht? also abgesehen von "arbeitsersparnis", weil das schon jemand anderes gemacht hat, fällt mir da jetzt nichts auf.

    Überleg mal was passiert wenn das zweite new[] wirft.


  • Mod

    @Wade1234 sagte in array mit Zahlen füttern und sortieren:

    @Swordfish
    naja die container machen das intern doch genauso, oder nicht? also abgesehen von "arbeitsersparnis", weil das schon jemand anderes gemacht hat, fällt mir da jetzt nichts auf.

    Sie machen's auch garantiert richtig. Ich würde viel Geld darauf verwetten, dass du es nicht schaffen würdest, eine 100% korrekte Umsetzung zu implementieren.



  • ja das ist mir auch klar. aber das grundsätzliche verständnis sollte man schon haben, oder?


  • Mod

    @Wade1234 sagte in array mit Zahlen füttern und sortieren:

    ja das ist mir auch klar. aber das grundsätzliche verständnis sollte man schon haben, oder?

    Wo ist denn hier das Verständnis? Bloßes Wissen um die Existenz bringt ja nichts, wenn die Schlüsselerkenntnis fehlt, wie man es korrekt benutzt. In dem Fall wäre es sogar besser, gar nichts von der Existenz gewusst zu haben, dann kann man es wenigstens nicht falsch machen.

    new gehört in eine ähnliche Schublade wie longjmp. Jede Erklärung, die nicht erklärt, wie man es richtig benutzt, und dass man es so gut wie nie braucht, ist unvollständig.



  • @Wade1234 sagte in array mit Zahlen füttern und sortieren:

    naja sobald die jeweiligen anweisungsblöcke verlassen werden, verlieren m3, m2 und m1 ihre gültigkeit, wodurch der darin vorhandene string auch ungültig wird, was den aufruf des destruktors von string und dadurch die freigabe der ressourcen zur folge hat.

    @Wade1234
    Jaein. Sobald beispielsweise m3 seine Gültigkeit verliert, wird der Destruktor aufgerufen. Dieser schreibt die Meldung "Block 3 ends after 1045 ms!". Nachdem der Destruktor fertig ist, verlieren m3.mMessage und m3.mStartPoint ihre Gültigkeit, s.d. auch deren Destruktoren aufgerufen werden. Der Destruktor von mMessage ruft bei mir übrigens _M_dispose() auf, was wohl den belegten Speicher freigibt.

    Ein Tipp: Nimm meinen Code, importiere diesen in einer IDE, setze Breakpoints bei den std::clog Anweisungen und debugge den Code. Das dürfte dir mehr helfen.


Anmelden zum Antworten