container mit nicht-kopierbaren elementen



  • wie muss ich das machen wenn ich objekte habe die sich mit einem konstruktor erstellen lassen aber nicht kopier- / zuweisbar sind? also was muss ich da für einen container nehmen? die ganze sache mit emplace_back von c++11 fällt schonmal weg. der konstruktor nimmt btw immer nur einen parameter an. kann man da was eigenes kurz basteln oder so? oder gibts das bereits?



  • Eine Möglichkeit ist, die entsprechenden Objekte auf dem Heap zu erstellen und im Container nicht die Objekte selbst, sondern Zeiger darauf zu speichern.
    Ist allerdings etwas fehleranfällig, weil der Container beim Entfernen von Elementen(Zeigern), oder bei seiner eigenen Zerstörung die Objekte nicht zerstört, darum muss man sich dann selbst kümmern, oder aber einen entsprechenden Wrapper um den Container schreiben, oder von dem Container erben und die entsprechende Funktionalität einbauen.



  • Die Objekte müssen auf jeden Fall moveable sein.
    Sonst musst du sie per (Smart!)Pointer speichern.



  • Belli schrieb:

    ...von dem Container erben...

    🙄 Sprechen wir hier Java?



  • Sorry, ich dachte Vererbung wäre auch ein Konzept in C++ ...



  • Belli schrieb:

    Sorry, ich dachte Vererbung wäre auch ein Konzept in C++ ...

    Nur so lange der Baseclass Destructor virtual ist.
    Inherit to be reused, not to reuse!


  • Mod

    Belli schrieb:

    Sorry, ich dachte Vererbung wäre auch ein Konzept in C++ ...

    Und eindeutig unpassend in diesem Zusammenhang, denn ein Container der auf nicht-kopierbare Elemente ausgelegt ist, ist nicht LSP-kompatibel mit einem Container, der dies nicht ist.
    Abgesehen von möglichen technischen Schwierigkeiten, da die Container der STL weder protected-Member haben, noch einen virtuellen Destruktor und somit nicht darauf ausgelegt sind, irgendjemanden zu beerben.



  • Ich frag mich auch immer weider wieso das die ganzen Javainer machen.

    Ich mein, wenn ich ne Liste von irgendwas brauche, dann hol ich mir die als Member. Aber davon erben ... ne bah



  • Ich spreche nicht von einem Container, der auf nicht-kopierbare Elemente ausgelegt ist - schließlich sind Zeiger kopierbar.
    Warum sollte es mich stören, dass die Basisklasse keinen virtuellen Konstruktor hat? Ich habe nicht vor, mit einem Zeiger auf die Basisklasse des abgeleiteten Containers oder in anderer Form mit der Basisklasse zu hantieren.
    Zu guter Letzt:
    Ich habe von Java keine Ahnung, und ich besitze 'Die C++ Programmiersprache', worin Herr Stroustrup selbst ein Beispiel von einer von vector abgeleiteten Klasse gibt.

    An die Boost-Kenner:
    So weit ich weiß, gibt es dort sowas wie einen Pointer-Vector. Wie wird das dort gemacht? Ohne von std::vector zu erben?

    Und noch eine Frage:
    In meiner Implementation ist vector abgeleitet von _Vector_val.
    Wieso gibt es da einen Unterschied zu der Situation, in der ich vector_pointer von vector ableite? Es gibt also sowieso schon den abgeleiteten Container ohne virtuellen Destruktor ...



  • Das Ding ist was vom Standard garantiert ist.

    Okay, hier auf deinem Rechner ist Vector vielleicht mit einem virtuellen Destruktor ausgestattet. Dann schreibst du Code der das nutzt oder sogar darauf baut. 2 Wochen später willst du den Code auf deiner Waschmaschine oder meinem Atomkraftwerk nutzen, der Compiler und seine STL dort implementieren den Standard aber strikter, haben demzufolge keinen Vector mit virtuellem Destruktor. Was passiert? Richtig, ein zweites Tschernobyl bzw deine Waschmaschine färbt deine Wäche pink.

    Ein anderer Punkt ist sauberes Design:
    Listen, Vectoren oder Arrays sind Utilities. Sie haben nichts mit irgendwas fachlichem zu tun, nie in keinster Weise.
    Durch das Ableiten von diesen spezialisierst du sie ja und bringst fachliches Wissen rein. Schreibst du dir einen eigenen Vector für Pointer ist das Ok. Schreibst du dir aber einen Vector für dein nicht kopierbares Objekt, dann ist das schon sehr speziell.

    Umgekehrt brauchen die Standardcontainer auch gewisse Vorbedingungen erfüllt damit sie ordentlich arbeiten können (genau wie dein eigener Container). Erfüll sie und du brauchst nix eigenes schreiben.



  • Skym0sh0 schrieb:

    Richtig, ein zweites Tschernobyl

    Ein drittes 🕶



  • Nein, nein, auch ich habe hier keinen virtuellen Destruktor. Ich bin nur der Meinung, dass ich den auch nicht benötige, so lange ich nicht mit Basisklassenzeigern herumhantiere.

    Und wenn ich mir einen Vector für Pointer durch Ableitung von std::Vector schreibe, dann ist der selbstverständlich speziell, limitiert (aber meiner Meinung nach nicht limitierter als vector - auch den sollte ich wohl nicht durch Verwaltung eines Basisklassenzeigers verwalten), und vor allen Dingen will ich diese Klasse ja nicht in einer Library oder sonstwie zur weiteren Verwendung anbieten ...



  • Belli schrieb:

    Nein, nein, auch ich habe hier keinen virtuellen Destruktor. Ich bin nur der Meinung, dass ich den auch nicht benötige, so lange ich nicht mit Basisklassenzeigern herumhantiere.

    Das ist richtig. Abe wenn du dir so einen Container für Pointer schreibst und ihn dann natürlich oft benutzt, wächst die Gefahr, doch etwas falsch zu machen. Wenn mehrere Personen an einem Porjekt beteiligt sind, wird es noch gefährlicher. Dabei kann man das gleiche auch ganz einfach haben:

    std::vector<std::unique_ptr<MeineKlasse>> v;
    

    Oder, besser noch, man macht seine Klasse movable.


  • Mod

    Belli schrieb:

    An die Boost-Kenner:
    So weit ich weiß, gibt es dort sowas wie einen Pointer-Vector. Wie wird das dort gemacht? Ohne von std::vector zu erben?

    Bei Boost wird, wie es sein sollte, der unterliegende vector als Member gehalten. Und alles reimplementiert, da bei einem ptr_vector ohnehin praktisch jede Methode reimplementiert werden muss. Was ein starkes Zeichen dafür ist, dass hier Vererbung nicht das richtige Mittel ist. Siehe Auto und Motor. Bloß weil ein paar Methoden gleich heißen (und ein paar wenige auch identisch sind), macht das einen Motor noch nicht zum Auto.
    Womit wir beim nächsten Thema sind: Vererbung würde den ptr_vector/non_copyable_vector selbst zu einem vector machen.

    std::vector<void*> foo;
      boost::ptr_vector<int> bar;
      foo = bar;
    

    wäre dann möglich. Ebenso mit den nicht-kopierbaren Objekten. Aber man kann Container, die so etwas modellieren, eben nicht einfach als normalen Vektor auffassen, denn das würde all das kaputt machen, was diese Container eigentlich erreichen wollen. Das Liskovsche Substitutionsprinzip wird also verletzt. Das wiegt viel schwerer als ein fehlender virtueller Destruktor. Der virtuelle Destruktor ist eine technische Einzelheit, das was ich gerade beschreibe heißt, das diese Modellierung an sich falsch wäre.

    Als Alternative werfe ich mal private-Vererbung in den Raum. Die bringt die oben genannten Probleme nicht mit (ist keine "ist ein"-Relation), erlaubt aber die einfache "Wiederverwertung" vorhandener Methoden.



  • Zu diesem Prinzip (welches ich nicht kenne/kannte) finde ich bei Wikipedia:

    Das liskovsche Substitutionsprinzip (LSP) oder Ersetzbarkeitsprinzip ist ein Kriterium in der objektorientierten Programmierung, das die Bedingungen zur Modellierung eines Datentyps für seinen Untertyp angibt. Es besagt, dass ein Programm, das Objekte einer Basisklasse T verwendet, auch mit Objekten der davon abgeleiteten Klasse S korrekt funktionieren muss, ohne dabei das Programm zu verändern.

    Nun wäre meine abgeleitete Klasse ja

    class ptrvector : public vector<meinTyp*>
    {}
    

    und ich würde Objekte der Klasse vector<meinTyp*> gar nicht benutzen, so dass streng genommen das oben aufgeführte Prinzip gar nicht verletzt werden kann?!



  • testmensch schrieb:

    die ganze sache mit emplace_back von c++11 fällt schonmal weg. der konstruktor nimmt

    Was meinst du damit? Verwendest du kein C++11? std::list mit emplace_back würde nämlich genau das machen: Einmal erzeugen, automatisch entfernen, weder kopieren noch verschieben.

    Also machst du entweder deine Klasse moveable (erfordert C++11) und kannst sie trotzdem verwenden oder du nutzt boost::ptr_container oder einen container aus unique_ptr.


  • Mod

    Belli schrieb:

    ich würde Objekte der Klasse vector<meinTyp*> gar nicht benutzen, so dass streng genommen das oben aufgeführte Prinzip gar nicht verletzt werden kann?!

    Dass du dich als Programmierer beherrschen kannst, die möglichen Fehler nicht zu begehen, das glaube ich dir. Das Problem ist jedoch, dass du nun die Möglichkeit dieser Fehler überhaupt erst geschaffen hast. Das heißt, jeder muss ungeheuer aufpassen, wenn er deinen Code benutzt, dass er nicht über die von dir fahrlässig hinterlassenen Fallstricke stolpert. Mit dem Argument, das du es im Moment für dieses eine Programm richtig machst, könntest du sonst jede Perversion der Sprache und jeden noch so verworrenen Spagetticode rechtfertigen. Hauptsache läuft?
    Gerade bei einem wiederbenutzbaren Containerklassentemplate ist einmalige, vorsichtige Benutzung ja wohl überhaupt nicht gegeben.



  • ja ich verwende kein c++11, daher fällt list und emplace_back weg (das war mir schon im voraus bewusst), auch die klasse moveable zu machen fällt weg. unique_ptr ist nicht so in c++03 zu implementieren dass man es in einem vektor speichern kann oder? sonst bleibt mir nur ein vector aus shared_ptr oder ein ptr_vector aus boost übrig.



  • Belli schrieb:

    In meiner Implementation ist vector abgeleitet von _Vector_val.
    Wieso gibt es da einen Unterschied zu der Situation, in der ich vector_pointer von vector ableite? Es gibt also sowieso schon den abgeleiteten Container ohne virtuellen Destruktor ...

    OMG

    Es geht nicht um den fehlenden virtuellen Destruktor, sondern darum, dass std::vector einfach nicht darauf ausgelegt ist "subclassed" zu werden.
    (EDIT: Der nicht-virtuelle Destruktor ist bloss ein Wink mit dem Zaunpfahl dahingehend dass std::vector eben nicht darauf ausgelegt ist dass man davon ableitet.)

    D.h. du kannst von std::vector ableiten, grundsätzlich kein Problem. So lange du nicht versuchst bestehende Funktionalität in irgend einer Art und Weise zu ändern. Oder Funktionen wie push_back etc. zu überdecken, um z.B. irgendwas mitzubekommen. Weil das eben nicht geht.
    (Die std::vector Funktionen sind nicht virtual, also kann man nix überschreiben, also kann man sich auf nix verlassen.)

    Beispielsweise können bestimmte STL Algorithmen auf std::vector spezialisiert sein. Jetzt können zwei Dinge passieren:

    1. Die Spezialisierung greift nicht mehr, weil z.B. ein Function-Template spezialisiert wurde (im Gegensatz zu einem Overload mit einem weiteren Function-Template).
      Dann wird die "falsche" Version des Algorithmus ausgeführt.
    2. Die Spezialisierung greift, aber bekommt eine Referenz vom statischen Typ std::vector<...>& mit. Und ruft über diese die nicht-virtuellen Funktionen auf (push_back, was auch immer). Also die von std::vector, und NICHT deine.

    Versuch einfach mal es zu machen. Dann probier folgende einfache Dinge:
    * Füg' ein Element mit std::back_inserter ein
    * "Lösch" ein paar Elemente mit std::remove_if OHNE nachfolgendem std::vector::erase
    * Lösch ein paar Elemente mit std::remove_if MIT nachfolgendem std::vector::erase
    Und guck ob irgendwelcher Unsinn dabei rauskommt.

    Weiters ist std::vector kopierbar. D.h. man kann aus deinen abgeleiteten Vektor einen normalen std::vector kopieren. Macht auch irgendwie keinen Sinn wenn dein abgeleiteter Vektor besitzende Zeiger enthält.

    => Die STL Container sind nicht darauf aus dass man von ihnen Ableitet.

    Dass die STL std::vector selbst so implementieren darf, dass std::vector von irgendeiner internen, nicht dokumentierten, nicht standardisierten und vor allem mit einem Reserved-Name versehenen Hilfsklasse erbt, spielt dabei überhaupt keine Rolle. Das sind Implementierungs-Details um die du dich nicht zu kümmern brauchst.


Anmelden zum Antworten