std::vector - Ableitung/Komposition



  • Hallo zusammen,

    ich hoffe meine Frage ist nicht allzu dumm... :?

    Es geht darum, das ich einen std::vector als Komposition benutzen möchte aber nicht weiß, wie ich dann über einen range iterieren kann.

    Beispiel wenn ich ableiten - ich weiß, dass ich die Klasse wegen des fehlenden viruellen Destruktors dann nicht polymorph verwenden kann:

    template <class T>
    class MyVector : public std::vector<T>
    

    jetzt kann ich natürlich problemlos über eine Range iterieren:

    MyVector<int> v;
    
    for ( int i : v)
    {.....
    

    Jetzt will ich den Vekor aber als Komposition benutzen (sagt man das so?):

    template <class T>
    class MyVector 
    {
     private std::vector<T> vec;
    

    Wie kann ich nun über einen Bereich iterieren, also genau wie zuvor:

    MyVector<int> v;
    
    for ( int i : v)
    {.....
    

    Vielen Dank für eure Hilfe 🙂



  • Graythorn schrieb:

    ich hoffe meine Frage ist nicht allzu dumm... :?

    Das ist sie nicht.

    Komposition:

    template<class T>
    class MyVector
    {
    public:
       typedef typename std::vector<T>::iterator iterator;
       typedef typename std::vector<T>::const_iterator const_iterator;
       ...
       iterator begin() { return data.begin(); }
       iterator end() { return data.end(); }
       const_iterator begin() const { return data.begin(); }
       const_iterator end() const { return data.end(); }
       ...
    private:
       std::vector<T> data;
    };
    

    So ist das aber etwas mühselig. Man müsste für "..." noch den Rest der Schnittstelle kapseln, damit der Nutzer auch was von seinem MyVector hat.

    Alternativ ginge auch "implementation inheritance":

    template <class T>
    class MyVector : private std::vector<T> {
       typedef std::vector<T> stdvec;
    public:
       // ein paar typedefs aus der Basisklasse zur Verfügung stellen
       using iterator = typename stdvec::iterator;
       using const_iterator = typename stdvec::const_iterator;
       using reference = typename stdvec::reference;
       using const_reference = typename stdvec::const_reference;
       using size_type = typename stdvec::size_type;
    
       // ein paar methoden aus der Basisklasse zur Verfügung stellen
       using stdvec::stdvec; // Konstruktoren
       using stdvec::begin;
       using stdvec::end;
       using stdvec::insert;
       using stdvec::erase;
       using stdvec::size;
       using stdvec::push_back;
    
       // mal selbst dazwischen funken
       reference operator[](size_type index)
       {
           std::cout << "huhu\n";
           return stdvec::operator[](index);
       }
    };
    

    Man leitet also privat ab. Damit sind auch automatisch alle Member der Basisklasse privat. Der Nutzer kann jetzt nicht mehr aus einem MyVector<T>* ein std::vector<T>* machen. Per using-Deklarationen, kann man dem Nutzer aber einige Member wieder zur Verfügung stellen. Ist eine Funktion überladen, schließt das auch gleich alle Überladungen mit ein (mehrere Konstrucktoren, beide push_back Varianten, etc). Das ist also weniger schreibaufwändig.

    Und selbst "public inheritance" ist eigentlich nicht so schlimm. Es kommt ein bisschen drauf an, was Dein MyVector anders machen soll. Wenn es nicht schlimm ist, dass der Nutzer direkt an die Basisklasse dran kann, dann kannst du auch öffentlich erben. Polymorph ist da dann nichts. Aber das wäre nur dann schlimm, wenn Du ein dynamisch erzeugten MyVector über einen std::vector zeiger löschen willst. Wenn Du das vermeidest, ist alles OK. Mit public inheritance würdest Du dir diese using-Deklarationen sparen. Ist jetzt nicht "schön", aber geht.


  • Mod

    Ich habe mal vor einer halben Ewigkeit obiges als notwendig beschrieben, und volkard antwortete prompt, dass es ein Design Fehler sei.



  • Arcoth schrieb:

    Ich habe mal vor einer halben Ewigkeit obiges als notwendig beschrieben,

    Was genau?

    Arcoth schrieb:

    und volkard antwortete prompt, dass es ein Design Fehler sei.

    Zum Design kann ich jetzt nicht viel sagen. Ich weiß nicht, was Graythorn mit MyVector überhaupt erreichen will. Aber das ist mal eine gute Gelegenheit, nachzuhaken: Graythorn, was ist deine Motivation für MyVector ?



  • template <class T>
    class MyVector : public std::vector<T>
    //Hier fehlt die Angbabe, was dazukömmt
    
    template <class Tier>
    class MyZoo : public std::vector<Tier>
    //Designfehler
    

    Auch wenn sich alle Tiere kopieren lassen, der Zoodirektor Grzimeck dann doch nicht. Da führt nur zu Ärger.

    Du hast doch irgendwas vor, wenn Du von einem std::vector erben willst.

    Was spricht bei Deinem konkreten Problem dagegen, zu sagen, daß Dein Zoo einen vector<Tiere> hat anstatt ein vector<Tiere> zu sein?

    template <class Tier>
    class MyZoo
    {
        public std::vector<Tier> tiere;
    public:
        std::vector<Tier> const& getTiereView() const{//gucken darf jeder
            return tiere;
        }
    
    //Designfehler
    

    Ich verstehe nicht, wie Du gleichzeitig "als Komposition" benutzen willst mit "for(auto& t:zoo)" statt "for(auto& t:zoo.getTiereView());". Wir hatten uns doch geeinigt, daß der Zoo nicht eine Ansammlung von Tieren IST. Warum es dann für die Schaleife voraussetzen?

    https://www.c-plusplus.net/forum/75672-full



  • Ein kl. Einschub:
    Von STL Containern abzuleiten ist keine sonderlich gute Idee.

    vgl. Diskussion:

    Nathan schrieb:

    jb2603 schrieb:

    Warum haben die STL Container kein virtuellen Destruktor?

    Antwort: Sie haben keine virtuellen Funktionen und sind nicht als Basisklasse konzipiert. Man sollte deshalb auch nie von ihnen erben.

    Q: https://www.c-plusplus.net/forum/335150?highlight=vector+virtual



  • Ich habe 3 Gründe, um den Container (hier vector) zu kapseln:

    1. Ich habe einen älteren Programmcode zu warten, und der benutzt eben keinen STL-Container. Daher kapsele ich diesen, um die im alten Code benutzten Methoden nachzubilden. Nach und nach stelle ich so auf die STL-Container um.

    2. Ich möchte einige spezielle Funktionalitäten hinzufügen.

    3. Ich möchte die Klasse kapseln, um somit von einer speziellen Implementierung (hier STL) unabhängig zu sein.

    Ich habe noch eine (Verständnis-)Frage an die Experten:

    warum schreibe ich

    typedef int myType_t;
    

    aber

    typedef typename std::vector<T>::iterator     iterator;
    

    also mal mit und mal ohne "typename"?
    Wo ist der Unterschied, wann benötige ich "typename"?

    Danke für die Hilfe und Diskussion 🙂


  • Mod

    Graythorn schrieb:

    Wo ist der Unterschied, wann benötige ich "typename"?

    http://pages.cs.wisc.edu/~driscoll/typename.html



  • Graythorn schrieb:

    Ich habe 3 Gründe, um den Container (hier vector) zu kapseln:

    Du unkapselst aber.

    Den return-Typ von getTiereView() kannste frei bestimmen, es gibt kein Gesetz, daß es vector sein muss, glaube ich.


Log in to reply