map am ende löschen?



  • Helium schrieb:

    Dann machs vorher.

    genau.
    und der container hat doch eh nen wrapper drum, um anwendungsnamen wie Personalliste auf std-namen wie str::map<int,Person*> zu mappen.
    der kann das fein machen.

    void Personal::toeten(int id){
       delete leute[id];
       leute.erase(id);
    }
    


  • volkard schrieb:

    haste dir schonmal gedanken gemacht, wie du die kraft von c++ noch viel voller ausschöpfen kannst? indem du java benutzt.

    Ich finde Java als Sprache einfach impotent - da verwende ich noch lieber den nicht optimierenden Standard-VC (oder vielleicht Ruby). Ist es denn so exotisch, dass man C++ als schöne und mächtige Sprache, nicht nur als Assembler mit templates ansieht?

    in c++ muß man nicht umständlich ans delete denken. in c++ denkt man IMMER ans delete. ganz einfach ist das. zu jedem new gehöer ein delete. immer. immer. immer.

    Ich fahre mit der Regel "es gibt kein ungewrapptes delete. Nie." auch ganz gut und kann mich spontan an keine Bugs erinnern, die ich auf der Ebene gehabt habe. Vielleicht brauche ich ja noch ein paar Monate mehr ohne delete, damit ich vergesse, wo Löschungen statt finden... 🙂



  • operator void schrieb:

    Ich fahre mit der Regel "es gibt kein ungewrapptes delete. Nie."

    halte ich für übertrieben. denn so oft muß man new/delete nu wirklich nicht anfassen. aber wenn du immer smart pointers benutzt, schmeißt du unnötig viel performance weg. ich hab glaub ich schon gepostet, wo man normalerweise die deletes hinversteckt. und das geht ganz ohne smart-pointers. und es geht ganz harmonisch, weil c++ nicht als besserer makroassembler benutzt wurde.
    share_ptr sind wirklich selten vonnöten. ganz selten. ungeheuter selten, um es auf den punkt zu bringen.
    es ist kein witz, daß ich dir empfehle, java zu benutzen. offensichtlich liegt dir c++ nicht. du wirst nur ärger bekommen, wenn du die sprache vergewaltigst. du mußt rausfinden, wie man in der zielsprache am besten programmiert. nicht völlig losgelöst rausfinden, wie man gut programmiert und dann hoffen, daß es kein schuß ins knie wird.



  • operator void schrieb:

    Vielleicht brauche ich ja noch ein paar Monate mehr ohne delete, damit ich vergesse, wo Löschungen statt finden... 🙂

    ist das dein wunsch? ich kenne die lösung...
    du kannst sogar vergessen, wann die löschungen stattfinden.



  • Nö. Ich verwende Smart-Pointer nur, weil ich weiß, was sie wann tun. Das ist aber nicht weiter schwer festzustellen. Ich kenne jedenfalls keinen verbreiteten, der nicht leicht vorhersehbar wäre.

    volkard schrieb:

    halte ich für übertrieben. denn so oft muß man new/delete nu wirklich nicht anfassen. aber wenn du immer smart pointers benutzt, schmeißt du unnötig viel performance weg.

    Ich sagte nicht, dass ich immer shared_ptr verwende. Wo schmeißt ein scoped_ptr Performance weg?
    shared_ptrs brauche ich in der Tat seltener. Und wenn ich sie brauche, dann aber auch oft an Stellen, an denen ein GC nicht helfen würde, sondern ich den Inhalt eines vectors von shared_ptrs z.B. wirklich sicher tot haben will, wenn der vector auch tot geht. Und nie an Stellen, an denen sie der Performance schaden. Mir fällt jedenfalls keine ein.

    Ich fühle mich in C++ eigentlich auch nicht verkehrt. Wenn man sein delete nicht selbst schreibt, sind auf einmal z.B. auch Exceptions kein Problem mehr, sondern funktionieren einfach genau richtig. Aber naja, dass ich nach dem Projekt hier auch mal andere Sprachen ansehen werde, wird dich sicher beruhigen.



  • operator void schrieb:

    Ich fühle mich in C++ eigentlich auch nicht verkehrt.

    Geht mir genauso und ich verwende auch Smart-Ptr und andere RAII-Klassen (und boost und andere Sachen die das Leben leichter, aber nicht zwangsweise langsamer machen).
    Und wenn ich mir den Kopf über Performance zerbreche, versuche ich auch ganz gerne so Evergreens wie die 80-20-Regel zu beachten...

    Hm, allerdings werde ich mir auch nicht von Volkard das C++ Programmierern ausreden lassen 😉



  • operator void schrieb:

    Ich sagte nicht, dass ich immer shared_ptr verwende. Wo schmeißt ein scoped_ptr Performance weg?

    kann sein, daß er keine wegschmeißt. jo, mit der schnittstelle ist man nicht gezwungen, was teures zu machen. also falls man echt pimpl machen muss, sollte man wohl auch scoped_ptr nehmen.
    bin gerade am überlegen, wann ich zum letzten, mal pimpl machen musste. öhm. hab's vergessen.
    wann sollte ich pimple benutzen? warum tut ihr das alle? bloß um die compilezeit zu senken?

    ich hab resentiments gegen smart pointers, weil ich keine gute verwendung sehe. außer mal in kranken ausnahmen, sagen wir mal nicht häufiger als einmal im jahr. und das liegt nicht daran, daß ich smart pointers nicht kennen würde.

    shared_ptrs brauche ich in der Tat seltener. Und wenn ich sie brauche, dann aber auch oft an Stellen, an denen ein GC nicht helfen würde, sondern ich den Inhalt eines vectors von shared_ptrs z.B. wirklich sicher tot haben will, wenn der vector auch tot geht. Und nie an Stellen, an denen sie der Performance schaden.

    jup. refcounting pointers hab ich bisher nur in multithreaded umgebenungen gebraucht, wenn die threads sich die objekte gegenseitig schicken, nur um selber möglichst wenig tun zu müssen. da kann ich nie genau wissen, wie viele threads das objekt gerade kennen.
    es könnte aber sein, daß ich demnächst total viel refcounting mache, umd total viel speed zu kriegen.

    Ich fühle mich in C++ eigentlich auch nicht verkehrt. Wenn man sein delete nicht selbst schreibt, sind auf einmal z.B. auch Exceptions kein Problem mehr, sondern funktionieren einfach genau richtig.

    ich hab auch nie rohe zeiger in der hand. und ich muss nie was catchen, nur um was freizugeben.

    Aber naja, dass ich nach dem Projekt hier auch mal andere Sprachen ansehen werde, wird dich sicher beruhigen.

    och, ist eigentlich egal. java kommt ja im managed käppchen ja auch zu jedem c++-programmierer.



  • volkard schrieb:

    bin gerade am überlegen, wann ich zum letzten, mal pimpl machen musste. öhm. hab's vergessen.
    wann sollte ich pimple benutzen? warum tut ihr das alle? bloß um die compilezeit zu senken?

    Spontan fallen mir drei Gründe ein
    1. Um Transaktionen leichter implementieren zu können
    2. Dazu passend: Um einen exception-sicheren op= für beliebige Klassen implementieren zu können (-> über ein nonthrowing Swap) -> Siehe: "More Exceptional C++" - Item 22
    3. Für Implicit-Sharing (-> siehe Qt)

    Die Compilezeit-Geschichte hat mir bei meinem letzten größeren Projekt aber auch einige Stunden Wartezeit erspart (gcc 3.2 auf einem 200 Mhz Pentium II 128 MB. Das dauert...)



  • volkard schrieb:

    kann sein, daß er keine wegschmeißt. jo, mit der schnittstelle ist man nicht gezwungen, was teures zu machen. also falls man echt pimpl machen muss, sollte man wohl auch scoped_ptr nehmen.

    Tu ich. Und auch für Klassenelemente oft aus diversen Gründen (0/1-Komposition ist der Einleuchtendste).

    bin gerade am überlegen, wann ich zum letzten, mal pimpl machen musste. öhm. hab's vergessen.
    wann sollte ich pimple benutzen? warum tut ihr das alle? bloß um die compilezeit zu senken?

    Jup, das ist definitiv der Hauptgrund. Aber ich finde es auch nicht weiter schlimm/umständlich, zu pimplen - man schreibt das Interface in den Header und arbeitet danach nur an der .cpp. Meistens hab ich das Header-Tab gar nicht offen, wenn ich die Klasse implementiere.
    Das Effizienz-Gegenargument bleibt natürlich bestehen, aber ich nehme für die Mehrproduktivität durch kurze Compilezeiten gerne den Overhead in Kauf. (Geht ja wieder nur um bestimmte Klassen. Wo Effizienz spürbar zählt, orientiere ich mich natürlich auch stärker nach ihr.)

    ich hab resentiments gegen smart pointers, weil ich keine gute verwendung sehe. außer mal in kranken ausnahmen, sagen wir mal nicht häufiger als einmal im jahr. und das liegt nicht daran, daß ich smart pointers nicht kennen würde.

    Wenn du weder smart pointers noch delete häufig brauchst, wüsste ich gerne mal, was dann. Es ist nicht so, als ob ich versuchen würde, möglichst viel new einzusetzen 🙂

    ich hab auch nie rohe zeiger in der hand. und ich muss nie was catchen, nur um was freizugeben.

    Naja, nehmen wir ein einfaches:

    void Personal::ersetzeAngestellten(int id, string infodateiname)
    {
        delete map[id];
        map[id] = new Angestellter(infodateiname);
        // Hups, Datei gibts nicht. Exception fliegt, ~Personal löscht nen den alten
        // Eintrag nochmal und das Programm wird mit einer schicken Zugriffsverletzung
        // abgeschosen.
    }
    

    Ist in dem (konstruierten, um am Beispiel zu bleiben) Fall vielleicht eh egal, aber zumindest würde ich mir beim Coden öfter Gedanken machen: Was wäre _wenn_? Mit shared_ptr wäre das

    map[id].reset(new Angestellter(infodateiname));
    

    Und fertig. Ich kenne Leute, die Exceptions in Konstruktoren aus solchen Gründen verteufeln und lieber nochmal .Create() aufrufen. Ist das im Sinne von C++?



  • operator void schrieb:

    Tu ich. Und auch für Klassenelemente oft aus diversen Gründen (0/1-Komposition ist der Einleuchtendste).

    damit ich mir das besser vorstellen kann, bei welcher klasse zuletzt?

    void Personal::ersetzeAngestellten(int id, string infodateiname)
    {
        delete map[id];
        map[id] = new Angestellter(infodateiname);
        // Hups, Datei gibts nicht. Exception fliegt, ~Personal löscht nen den alten
        // Eintrag nochmal und das Programm wird mit einer schicken Zugriffsverletzung
        // abgeschosen.
    }
    

    komisch. mache ich mir nie gedanken um exception sicherheit? irgendwie sehe ich zwar das problem, aber es ist nicht normal für mich.

    void Personal::ersetzeAngestellten(int id, Angestellter* angestellter)//nothrow
    {
        delete map[id];
        map[id] = angestellter;
    }
    

    und wenn gründe dafür sprechen, dem user ne andere schnittstelle zu geben, meinetwegen auch

    void Personal::ersetzeAngestellten(int id, string infodateiname)
    {
        erstezeAngestellten(id, new Angestellter(infodateiname));
    }
    

    Ist in dem (konstruierten, um am Beispiel zu bleiben) Fall vielleicht eh egal, aber zumindest würde ich mir beim Coden öfter Gedanken machen: Was wäre _wenn_? Mit shared_ptr wäre das

    map[id].reset(new Angestellter(infodateiname));
    

    ok, klarere punkt für "sich gedanken soll man machen" aber nicht zwingend für shared_ptr.

    Ich kenne Leute, die Exceptions in Konstruktoren aus solchen Gründen verteufeln und lieber nochmal .Create() aufrufen. Ist das im Sinne von C++?

    ganz sicher nicht. Create verschiebt das problem nur, hab ich das gefühl.



  • volkard schrieb:

    damit ich mir das besser vorstellen kann, bei welcher klasse zuletzt?

    Ich habe gerade ein Spielobjekt, in das der Benutzer im Editor ein Bild laden kann. Da er das Objekt erst platzieren muss, bevor er seine Eigenschaften ändern kann, muss es leider einen Leerzustand geben, den du ja nicht unbedingt magst.
    Oder die Kartenteile, die acht optionale Randerweiterungen in einem scoped_ptr<Edge>[8] halten. Oder das Spielbasisobjekt, dass seine Eventverwaltung in einem scoped_ptr speichert und erst bei Bedarf erstellt (um die Masse der Objekte, die keine Events haben, klein zu halten). Naja, ist halt ein Spiel. Vielleicht ist das Beispiel ja trotzdem ernst zu nehmen.

    Ansonsten verwende ich scoped_ptr auch sehr oft, wenn ein Objekt nicht schon in der Initialisierungsliste erstellt werden kann, weil die Argumente erst im Konstruktor zusammengebaut werden müssen. Aber sehr schön finde ich das ehrlich gesagt nicht; das ist aber eher ein Workaround um die nervige Regel, dass man Objekte nicht frei im Konstruktorcode initialisieren kann.

    komisch. mache ich mir nie gedanken um exception sicherheit? irgendwie sehe ich zwar das problem, aber es ist nicht normal für mich.

    Ich finde es auch sehr konstruiert in dem Beispiel und hab schon befürchtet, dass du das andere Interface bevorzugen würdest. Vielleicht ist das ja tückischer:

    Personal::Personal()
    {
        // Lade Standardangestellte
        vector<string> dateinamen = ladeAngestelltenListe();
        for (size_t i = 0; i < dateinamen.size(); ++i)
            ersetzeAngestellten(i, new Angesteller(dateinamen[i]));
    }
    

    Dann führt eine Exception zu einem Leak, weil ~Personal nicht ausgeführt wird. Damit das schlimm wäre, müsste Angestellter irgendwelche wichtigen Resourcen wie Datenbankhandles halten, oder so... Ich gebe zu: Ein Personalverwalter wird vermutlich einen nothrow-Konstruktor haben. Aber etwas allgemeiner betrachtet: Jedes Objekt, dass sich im Konstruktor mehrere Resourcen holt, die jeweils eine Exception beim Holen werfen können, muss theoretisch try-Blöcke drum setzen, weil der Destruktor die Arbeit nicht machen kann. Und das kommt, wenn auch nicht in Personalverwaltern, zumindest bei mir öfter vor.
    Wie auch immer: Man muss sich Gedanken machen, und mit smart-ptrs tue ich das nicht mehr und konzentriere mich auf andere Dinge.



  • operator void schrieb:

    Ich habe gerade ein Spielobjekt, in das der Benutzer im Editor ein Bild laden kann. Da er das Objekt erst platzieren muss, bevor er seine Eigenschaften ändern kann, muss es leider einen Leerzustand geben, den du ja nicht unbedingt magst.

    stimmt. mag den leerzustand nicht.

    Oder die Kartenteile, die acht optionale Randerweiterungen in einem scoped_ptr<Edge>[8] halten.

    *notizmach*

    Oder das Spielbasisobjekt, dass seine Eventverwaltung in einem scoped_ptr speichert und erst bei Bedarf erstellt (um die Masse der Objekte, die keine Events haben, klein zu halten). Naja, ist halt ein Spiel. Vielleicht ist das Beispiel ja trotzdem ernst zu nehmen.

    oh, in meinem Spielbasisobekt steht im dtor ein delete eventVerwaltung;.

    Ansonsten verwende ich scoped_ptr auch sehr oft, wenn ein Objekt nicht schon in der Initialisierungsliste erstellt werden kann, weil die Argumente erst im Konstruktor zusammengebaut werden müssen. Aber sehr schön finde ich das ehrlich gesagt nicht; das ist aber eher ein Workaround um die nervige Regel, dass man Objekte nicht frei im Konstruktorcode initialisieren kann.

    geht bei mir normalerweise, weil ich kleinere klassen habe.

    Aber etwas allgemeiner betrachtet: Jedes Objekt, dass sich im Konstruktor mehrere Resourcen holt, die jeweils eine Exception beim Holen werfen können, muss theoretisch try-Blöcke drum setzen, weil der Destruktor die Arbeit nicht machen kann. Und das kommt, wenn auch nicht in Personalverwaltern, zumindest bei mir öfter vor.

    bei mir nicht. wierd an eine-klasse-ein-zweck liegen. allerdings baue ich auch mal ne klassem, die eingeführt wird, um mir das aufräumen abzunehmen.

    Personal::Personal()
    {
        // Lade Standardangestellte
        vector<string> dateinamen = ladeAngestelltenListe();
        for (size_t i = 0; i < dateinamen.size(); ++i)
            ersetzeAngestellten(i, new Angesteller(dateinamen[i]));
    }
    

    in diesem fall hätte ich das pure besitzen der angestellten von restlicher verwaltung wie laden/speichern getrennt. die besitzende liste räumt sicher auf, egal, wie ich an andere stelle damit umgehe.
    dann hätte ich mir überlegt, ob ich das nicht recht oft brauchen könnte und nen adapter um vector gebaut, der für zeigerobjekte da ist, die beim vom-vector-gelöscht-werden ihren pointee deleten. eventuell. ich hab ja schon robleme, außer zeigern auch noch referenzen einzusehen. wie soll ich da noch 5 zeigersorten einsehen? aber container mit kleinen zusatzfeatures (policies?) find ich normal und klasse.

    notiz von oben: scoped_ptr<Edge>[8] ist natürlich jetzt nr, damit ich es leichter lesen kann. in wirklichkeit haste da ne array-klasse, die keine zusatzkosten hat, aber ein ASSERT im op[]. die würde ich noch mit nem parameter versehen, daß die zeiger mit 0 initialisiert werden und beim löschen deleted werden.



  • volkard schrieb:

    notiz von oben: scoped_ptr<Edge>[8] ist natürlich jetzt nr, damit ich es leichter lesen kann. in wirklichkeit haste da ne array-klasse, die keine zusatzkosten hat, aber ein ASSERT im op[]. die würde ich noch mit nem parameter versehen, daß die zeiger mit 0 initialisiert werden und beim löschen deleted werden.

    Wenn man boost::array ein assert() verpasst, ist boost::array<scoped_ptr<Edge>, 8> doch genau was du willst. Es ist beim Deklarieren mehr Tipparbeit, dafür hat es 0 Overhead und man muss keine eigene Klasse dafür schreiben. Und man muss weder beim Löschen, noch beim Neuzuweisen (in diesem Fall läuft das wirklich über edgeArray[3].reset(new Edge(...))) ein delete aufrufen... Warum soviel Arbeit machen?



  • in diesem fall hätte ich das pure besitzen der angestellten von restlicher verwaltung wie laden/speichern getrennt.

    Klingt bei allgemeinen Personalmanagern gut. Andererseits: Wenn der Personalmanager in der Anwendung wirklich nur dafür da ist, dass eine konkrete, persistente Liste von Angestellten verwaltet wird, hättest du damit nicht einen der allgemein vermeidenswerten Leerzustände, wenn du im Konstruktor nicht direkt lädst? Ich finds ja auch immer toll, wenn der Konstruktor schon die komplette Arbeit für mich macht.
    Aber zugegebener Maßen, ich glaube, das ganze Personal-Beispiel hilft mir in diesem Thread nicht viel. 😉

    volkard schrieb:

    Aber etwas allgemeiner betrachtet: Jedes Objekt, dass sich im Konstruktor mehrere Resourcen holt, die jeweils eine Exception beim Holen werfen können, muss theoretisch try-Blöcke drum setzen, weil der Destruktor die Arbeit nicht machen kann. Und das kommt, wenn auch nicht in Personalverwaltern, zumindest bei mir öfter vor.

    bei mir nicht. wierd an eine-klasse-ein-zweck liegen. allerdings baue ich auch mal ne klassem, die eingeführt wird, um mir das aufräumen abzunehmen.

    Naja. Bei sehr neutralen Klassen ist Eine-Klasse-Ein-Zweck gut durchführbar. Aber ein Spielobjekt, das im Konstruktor seine Grafiken und seine Sounds anfordert, hat schon zwei Resourcen, um mal wieder auf mein konkretes Beispiel zurückzukommen (weil ich mich da auskenne 😉 ).



  • operator void schrieb:

    Klingt bei allgemeinen Personalmanagern gut. Andererseits: Wenn der Personalmanager in der Anwendung wirklich nur dafür da ist, dass eine konkrete, persistente Liste von Angestellten verwaltet wird, hättest du damit nicht einen der allgemein vermeidenswerten Leerzustände, wenn du im Konstruktor nicht direkt lädst? Ich finds ja auch immer toll, wenn der Konstruktor schon die komplette Arbeit für mich macht.

    ?
    natürlich lade ich auch im konstruktor. die nur-besitzende-liste ist member von mir.
    oder um genauer zu sein, du machst ja deine entscheidungen davon abhängig, wieviel man tippen muss, ich erbe von der nur-besitzenden-liste.



  • operator void schrieb:

    Naja. Bei sehr neutralen Klassen ist Eine-Klasse-Ein-Zweck gut durchführbar. Aber ein Spielobjekt, das im Konstruktor seine Grafiken und seine Sounds anfordert, hat schon zwei Resourcen, um mal wieder auf mein konkretes Beispiel zurückzukommen (weil ich mich da auskenne 😉 ).

    ich mich zwar nicht, aber ich sehe das problem nicht bei

    class Spiel{
       Grafik grafik;
       Sound sound;
    };
    

    schwierig scheint es erst zu werden, wenn du mit zeugern umeinand wirfst, wo gar keine hingehören.
    oft schreibe ich

    main(){
       Grafik grafik;
       Sound sound;
       Spiel spiel(&grafik,&sound);
       ...
    }
    

    man müßte mal gründlich diskutieren, ob das spiel die grafik-engine und soundengine besizuen soll und erzeugen soll, oder ob es die engines nur benutzen soll. der thread muss über meh als 100 beiträge gehen, wenn wir's richtig machen wollen.



  • es muß andere gründe dafür geben, vector<scoped_ptr<Foo*>> zu benutzen. evtl besseres verhalten bei resize(), unique(), erase() oder solchen sachen? wenn ja, bei welchen und müßte man, wollte man nen vector anbieten, der beim wegwerfen von elementen noch ne zusätzliche destroy-sache aufruft, viel code anfassen oder könnte man im neen vector bleiben?



  • vector<scoped_ptr<Foo*>>
    

    Das geht doch gar nicht!
    Auf jeden Fall nicht wenn du boost::scoped_ptr meinst. Der ist nicht kopierbar.



  • no copy schrieb:

    vector<scoped_ptr<Foo*>>
    

    Das geht doch gar nicht!
    Auf jeden Fall nicht wenn du boost::scoped_ptr meinst. Der ist nicht kopierbar.

    richtig.
    die sprache war bloß von
    boost::array<boost::scoped_ptr<Edge>, 8>
    jo, da hab ich keine fragen, wie ein notboost::array<Edge,8,DeleteOnKill> funktionieren würde.



  • volkard schrieb:

    man müßte mal gründlich diskutieren, ob das spiel die grafik-engine und soundengine besizuen soll und erzeugen soll, oder ob es die engines nur benutzen soll. der thread muss über meh als 100 beiträge gehen, wenn wir's richtig machen wollen.

    Stimmt, aber zumindest will ich noch klären, dass ich mit Spielobjekt ein Objekt eines Spiels (also nen Stein oder Spieler oder...) meinte, nicht ein Objekt, das ein Spiel repräsentiert 🙂


Anmelden zum Antworten