map am ende löschen?



  • 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 🙂



  • wie war das? in nen vector kann ich scoped_ptr nicht stecken? in
    ein boost::array aber schon? jetzt mach ich mir aber gedanken, was
    los ist, wenn ich später mich mal enscheiden mag, daß ein array
    nicht fixed sized ist, sondern halt ein wenig auch mal seine größe
    ändern kann. dan mußte ich von scoped_ptr nach shard_ptr umsteigen.

    würde es klassen geben, wie ich sie mir vorstellen, dann könnte man
    einfach zwichen fixed-sized und var-sized anhand eines templateparameters
    umschalten und das wär's. ob daher meine verrückte annahme kommt, der
    container würde gut besitzer sein können und sich ums löschen kümmern
    müssen können?
    hattest mich schon so weit gehabt, daß ich mir vornahm, beim nächsten
    mal verschärft scoped_ptr einzusetzen und zu schauen, wie es sich
    bei seegang anfühlt. aber jetzt hab ich dazu gar keine lust mehr. ich
    muss ja viel zu viel aufpassen, wenn ich scoped_ptr benutze statt roher
    zeiger. nee, wenn der container verantworlich ist, ist es mir lieber.


Anmelden zum Antworten