vector mit unterschiedlichen Typen?



  • Hallo zusammen,

    ich wollte mal wissen, ob es generell eine Möglichkeit in C++ gibt, einen vector mit unterschiedlichen Datentypen zu füllen.
    In Java gibt es den "Vector" und der kann mit versch. Datentypen gefüllt werden - gibt es soetwas in C++ ebenfalls?



  • Du kannst den vector vom Typ void* machen.

    std::vector<void*> enthält einen Zeiger auf irgendein Objekt

    Bevor der Einspruch kommt wegen Zeigerfrickelei:
    Du kannst auch boost::any verwenden.

    EDIT: Allerdings braucht man sowas in der Regel nicht...

    Gruß,
    CSpille



  • Danke erstmal für die schnelle Antwort.

    Inzwischen hab ich mir auch etwas überlegt. Und zwar habe ich eine Klassenhierarchie und meine Überlegung ist, dass ich einfach einen std::vector der Basisklasse erstelle und dann kann ich diesen mit Objekten der abgeleiteten Klassen füllen.
    Gibt es da irgendwelche Einwände?

    So in etwa:

    std::vector<Basisklasse> baseVec;
    AbgeleiteteKlasse1 a;
    AbgeleiteteKlasse2 b;
    AbgeleiteteKlasse3 c;
    
    baseVec.push_back(a);
    baseVec.push_back(b);
    baseVec.push_back(c);
    

    Irgendwelche Risiken vorhanden? Der Compiler meldet keine Errors.



  • SeniiX schrieb:

    Danke erstmal für die schnelle Antwort.

    Inzwischen hab ich mir auch etwas überlegt. Und zwar habe ich eine Klassenhierarchie und meine Überlegung ist, dass ich einfach einen std::vector der Basisklasse erstelle und dann kann ich diesen mit Objekten der abgeleiteten Klassen füllen.
    Gibt es da irgendwelche Einwände?

    So in etwa:

    std::vector<Basisklasse> baseVec;
    AbgeleiteteKlasse1 a;
    AbgeleiteteKlasse2 b;
    AbgeleiteteKlasse3 c;
    
    baseVec.push_back(a);
    baseVec.push_back(b);
    baseVec.push_back(c);
    

    Irgendwelche Risiken vorhanden? Der Compiler meldet keine Errors.

    Finde ich die sauberere Lösung, als mit void*.



  • wie wärs mit Polymorphie?

    find ich noch saubeber, musst dich eben ums aufräumen kümmern...



  • Xantus schrieb:

    wie wärs mit Polymorphie?

    find ich noch saubeber, musst dich eben ums aufräumen kümmern...

    Er benutzt doch Polymorphie. 🙄



  • @this->that
    Also das denke ich nicht. Da er keine Zeiger verwendet oder Referezen verwendet. Siehe text an den OP.

    @SeniiX:

    Das mit dem vector und der Basisklasse kann nur dann funktionieren wenn du Zeiger auf die Basisklasse speicherst ansonsten kopierst du nämlich nur den Basisklassenteil deiner abgeleiteten klassen der rest ist dann futsch.

    Natürlich musst du, wenn du zeiger verwendest darauf achten das du den Speicher später auch wieder ordnungsgemäß freigibst. Hierfür könntest du einen smart pointer
    verwenden der mit Polymorphie klar kommt (z.b. boost::shared_ptr<>)

    BR
    Vinzenz



  • evilissimo schrieb:

    Das mit dem vector und der Basisklasse kann nur dann funktionieren wenn du Zeiger auf die Basisklasse speicherst ansonsten kopierst du nämlich nur den Basisklassenteil deiner abgeleiteten klassen der rest ist dann futsch.

    Natürlich musst du, wenn du zeiger verwendest darauf achten das du den Speicher später auch wieder ordnungsgemäß freigibst. Hierfür könntest du einen smart pointer
    verwenden der mit Polymorphie klar kommt (z.b. boost::shared_ptr<>)

    BR
    Vinzenz

    Könntest du mir dazu ein kleines Code-Bsp geben oder es mir näher erläutern, damit ich es auch wirklich korrekt ausprogrammieren kann?!



  • Das Problem ist folgendes:

    Wenn du eine Basisklasse B und eine Unterklasse C hast und jetzt eine Instanz vom Typ B mittels C instantiierst:
    B b(c);
    dann geht das zwar, aber es werden natürlich nur die B-Member von c nach b kopiert (die C Member kennt der B Copyctor ja garnicht). Das nennt man Slicing und ist unschön.

    Ferner hast du in deinem Beispiel keine Polymorphie. Sagen wir B hat eine Methode foo(), die in C überschrieben wird.
    Wenn du jetzt das machst:
    std::vector<B> v;
    v.add(c);
    und dann v[0].foo(); (sieht wohl irgendwie so aus, kenne die Syntax vom vector nicht auswendig)
    so wird IMMER das foo() der Basisklasse B aufgerufen, auch wenn du in Wirklichkeit eine Instanz vom Typ C ansprichst.

    Für Polymorphie brauchst du also Zeiger:
    std::vector<B*> v;
    v.add(new C(...));
    v.add(new B(...); usw.

    Dann funktioniert Polymorphie. Allerdings musst du bei einigen Algorithmen auf Containern aufpassen und auch selber den reservierten Speicher freigeben.



  • Typen:

    struct Basis{ virtual int getValue() const { return 0; } };
    struct Abgeleitet1 : Basis { virtual int getValue() const { return 1; } };
    struct Abgeleitet2 : Basis { virtual int getValue() const { return 2; } };
    

    Lösung 1:
    Zeiger selber verwalten
    - Sehr aufwendig
    - Fehleranfällig
    Und daher würde ich es auch als unsauber einstufen

    std::vector<Basis*> basis_vec;
    basis_vec.push_back(new Abgeleitet1());
    basis_vec.push_back(new Abgeleitet2());
    basis_vec.push_back(new Basis());
    
    // Erst Speicher der Zeiger freigeben
    for(std::vector<Basis*>::iterator it = basis_vec.begin(), end = basis_vec.end();    it != end; ++it)
      delete *it;
    // Dann zeiger löschen
    basis_vec.clear();
    

    Lösung2:
    boost::shared_ptr<> verwenden
    Ist immer noch sehr aufwendig und verursacht overhead (wegen den Smartpointern) aber immerhin weniger fehleranfällig für speicherlecks.

    std::vector<boost::shared_ptr<Basis> > basis_vec;
    basis_vec.push_back(boost::shared_ptr<Basis>(new Abgeleitet1()));
    basis_vec.push_back(boost::shared_ptr<Basis>(new Abgeleitet2()));
    basis_vec.push_back(boost::shared_ptr<Basis>(new Basis()));
    
    basis_vec.clear();
    

    Lösung3:
    Sauberste Lösung mit dem wenigsten overhead:

    // Braucht #include <boost/ptr_container/ptr_vector.hpp>
    boost::ptr_vector<Basis> basis_vec;
    basis_vec.push_back(new Abgeleitet1());
    basis_vec.push_back(new Abgeleitet2());
    basis_vec.push_back(new Basis());
    
    basis_vec.clear();
    

    BR
    Vinzenz



  • Braucht man das clear() zwingend? Sowas sollte ja eigentlich der Dtor machen.



  • this->that schrieb:

    Braucht man das clear() zwingend? Sowas sollte ja eigentlich der Dtor machen.

    Nein das war nur ein Beispiel um zu verdeutlichen was passieren muss wenn man daten aus dem vector löscht. Natürlich macht das der Destruktor.

    Aber der Destruktor gibt bei Lösung 1 eben nicht den Speicher der Zeiger frei. (Was dir sicherlich klar ist :))

    BR
    Vinzenz



  • Vielen vielen Dank, ich werde das mal so versuchen zu implementieren und bei Fragen komm ich eh wieder angekrochen 🙄



  • SeniiX schrieb:

    Vielen vielen Dank, ich werde das mal so versuchen zu implementieren und bei Fragen komm ich eh wieder angekrochen 🙄

    Macht doch nichts, wenn wir dir hier nicht Kompetent zu deinen Fragen helfen können, wo sonst? 😉

    BR
    Vinzenz



  • Wenn die Zahl der Typen, die du in dem Vektor speichern willst eine „natürliche“ Begrenzung hat, dann ist auch Boost.Variant erwähnenswert.

    Der Vorteil daran ist, dass es mit großer Wahrscheinlichkeit schneller ist als any, die Objekte auch tatsächlich im Vektor liegen (und nicht irgendwo auf dem Heap verstreut und bezeigt) und du mit einem (sehr intuitiv zu implementierenden) Visitor und std::for_each sehr schön die Daten weiterverarbeiten kannst.

    Der Nachteil ist, dass eben nur endlich viele Typen möglich sind und jedes Variant so groß ist, wie der größte darin speicherbare Typ.

    Konkret hab ich das mal für einen Midiparser verwendet, wobei ich die verschiedenen Midi-Ereignisse als einzelne Klassen implementiert habe, die von einander abgeleitet waren. Eine Spur wurde dann (relativ einfach) in einen Vektor mit solchen Variants geparst.

    Um nun die Spur zu verwenden, reicht es, einen passenden Visitor zu schreiben (der den operator() für geeignete Ereignisse überlädt (zb meta_event oder key_press), wobei auch die Vererbung berücksichtigt wird. Man kann beispielsweise recht einfach Filter schreiben, indem man nur für key_event (was key_press, key_release und key_aftertouch Basisklasse ist) o.ä. überlädt.



  • Kann ich 2 boost::ptr_vector normal mit dem =-Operator gleichsetzen?



  • Da ich beim Gleichsetzen per operator= immer einen Error bekomme, habe ich mir mal folgendes überlegt:

    Typen:

    class B
    {
       private:
          int Age;
    
       public:
          B( void ) { this->Age = 0; }
          ~B( void ) {}
    
          int getAge( void ) { return this->Age; }
          void setAge( const int &aAge) { this->Age = aAge; }
    
          virtual void speak( void ) { std::cout << "B speaking" << std::endl; }
    };
    
    class A1 : public B
    {
       public:
          A1( void ) {}
          ~A1( void ) {}
    
          virtual void speak( void ) { std::cout << "A1 speaking" << std::endl; }
    };
    
    class A2 : public B
    {
       public:
          A2( void ) {}
          ~A2( void ) {}
    
          virtual void speak( void ) { std::cout << "A2 speaking" << std::endl; }
    };
    

    Versuch den boost::ptr_vector zu kopieren:

    #include <iostream>
    #include <cstdlib>
    
    #include <boost/ptr_container/ptr_vector.hpp>
    
    int main(int argc, char **argv)
    {
       boost::ptr_vector<B> BVec1, BVec2;
    
       BVec1.push_back(new A1());
       BVec1.push_back(new A2());
       BVec1.push_back(new A1());
    
       for(int i=0; i<BVec1.size(); i++)
          BVec2.push_back(&BVec1[i]);
    
       for(int i=0; i<BVec1.size(); i++)
          BVec2[i].speak();
    
       BVec1.clear();
       BVec2.clear();
    
       return 0;
    }
    

    Da sich mein Programm jedoch immer aufhängt (am Ende) hab ich nochmal in der Dokumtation der boost library nachgeschaut und die Funktion "assign" gefunden und folgendes probiert:

    #include <iostream>
    #include <cstdlib>
    
    #include <boost/ptr_container/ptr_vector.hpp>
    
    int main(int argc, char **argv)
    {
       boost::ptr_vector<B> BVec1, BVec2;
    
       BVec1.push_back(new A1());
       BVec1.push_back(new A2());
       BVec1.push_back(new A1());
    
       BVec2.assign(BVec1.begin(), BVec1.begin());
    
       for(int i=0; i<BVec1.size(); i++)
          BVec2[i].speak();
    
       BVec1.clear();
       BVec2.clear();
    
       return 0;
    }
    

    Dieses Programm stürzt zwar nicht ab, allerdings kopiert "assign" irgendwie falsch, da ich bei der Ausgabe über "speak()" immer die Ausgabe "B speaks" bekomme, also die Funktion der Basisklasse aufgerufen wird.

    Kann mir jemand sagen, wie ich richtig zwei boost::ptr_vector<B> gleichsetzen kann? 😕



  • Dir ist anscheinend nicht klar, dass ptr_vector die Zeiger beim Zerstören auch löscht (das ist ja u.a. der Sinn der Sache). Damit ist auch der erste Fall klar: Beide vectoren enthalten nachher dieselben Zeiger, d.h. die Objekte werden alle doppelt gelöscht.

    Im zweiten Fall wird der ptr_vector wohl versuchen, die Objekte via new_clone zu kopieren, und die Standardimplementierung von new_clone läuft auf new B hinaus, da der vector zur Kompilierzeit nicht weiß, welche Typen tatsächlich gespeichert wurden. Dafür müsstest Du die Funktion B* new_clone( const B& ) anbieten und dort tatsächlich einen Klon von A1, A2 ... zurückgeben.



  • LordJaxom schrieb:

    Dafür müsstest Du die Funktion B* new_clone( const B& ) anbieten und dort tatsächlich einen Klon von A1, A2 ... zurückgeben.

    Danke erstmal für die schnelle und hilfreiche Antwort - echt super hier 🙂

    Ich habe deinen Rat befolgt und folgende Funktion "new_clone" ausprogrammiert:

    #include <iostream>
    #include <cstdlib>
    #include <typeinfo>    // für Typenüberprüfung
    
    #include <boost/ptr_container/ptr_vector.hpp>
    
    B *new_clone(const B &b)
    {
       if(typeid(b) == typeid(A1))
          return new A1();
    
       else if(typeid(b) == typeid(A2))
          return new A2();
    
       else if(typeid(b) == typeid(B))
          return new B();
    
       else
          return NULL;
    }
    
    int main(int argc, char **argv)
    {
       boost::ptr_vector<B> BVec1, BVec2;
    
       BVec1.push_back(new A1());
       BVec1.push_back(new A2());
       BVec1.push_back(new A1());
    
       for(int i=0; i<BVec1.size(); i++)
          BVec2.push_back(new_clone(BVec1[i]));
    
       for(int i=0; i<BVec2.size(); i++)
          BVec2.speak();
    
       BVec1.clear();
       BVec2.clear();
    
       return 0;
    }
    

    Mit dieser Funktion "new_clone" funktioniert es. Meine Frage ist nurmehr, ob ich sie verbessern kann bzw. ob sie Risiken hat?

    Danke schonmal für die Antwort 😉



  • Ok, beim Austesten der "new_clone"-Funktion ist mir jetzt selbst bereits ein Fehler aufgefallen. Angenommen wir haben folgende Typen:

    class B
    {
       protected:
          int Age;
    
       public:
          B( void ) { this->Age = 0; }
          B( const int &aAge) { this->Age = aAge; }
          ~B( void ) {}
    
          int getAge( void ) { return this->Age; }
          void setAge( const int &aAge) { this->Age = aAge; }
    
          virtual void speak( void ) { std::cout << "B speaking" << std::endl; }
    };
    
    class A1 : public B
    {
       private:
          int Weight;
    
       public:
          A1( void ) {}
          A1( const int &aWeight ) { this->Weight = aWeight; }
          ~A1( void ) {}
    
          int getWeight( void ) { return this->Weight; }
          void setWeight( const int &aWeight ) { this->Weight = aWeight; }
    
          virtual void speak( void ) { std::cout << "A1 speaking" << std::endl; }
    };
    
    class A2 : public B
    {
       private:
          int Height;
    
       public:
          A2( void ) {}
          A2( const int &aHeight ) { this->Height = aHeight; }
          ~A2( void ) {}
    
          int getHeight( void ) { return this->Height; }
          void setHeight( const int &aHeight ) { this->Height = aHeight; }
    
          virtual void speak( void ) { std::cout << "A2 speaking" << std::endl; }
    };
    

    Wenn ich nun Objekte mit bestimmten Werten initialisiere und in den boost::ptr_vector fülle habe ich ein Problem bei der new_clone-Funktion.

    #include <iostream>
    #include <cstdlib>
    #include <typeinfo>    // für Typenüberprüfung
    
    #include <boost/ptr_container/ptr_vector.hpp>
    
    B *new_clone(const B &b)
    {
       if(typeid(b) == typeid(A1))
          return new A1();
    
       else if(typeid(b) == typeid(A2))
          return new A2();
    
       else if(typeid(b) == typeid(B))
          return new B();
    
       else
          return NULL;
    }
    
    int main(int argc, char **argv)
    {
       boost::ptr_vector<B> BVec1, BVec2;
       const int init1 = 15, init2 = 20;
    
       BVec1.push_back(new A1());
       BVec1.push_back(new A2());
       BVec1.push_back(new A1(init1));
       BVec1.push_back(new A2(init2));
    
       for(int i=0; i<BVec1.size(); i++)
          BVec2.push_back(new_clone(BVec1[i]));
    
       for(int i=0; i<BVec2.size(); i++)
          BVec2[i].speak();
    
       BVec1.clear();
       BVec2.clear();
    
       return 0;
    }
    

    Da müsste ich wieder eine Unterscheidung treffen, allerdings vorher die B-Objekte in A1- bzw. A2-Objekte konvertieren um ihre Werte Weight bzw. Height per getWeight() oder getHeight() überprüfen zu können.

    Mein erster Gedanke war ein einfacher Cast:

    B *new_clone(const B &b)
    {
       if(typeid(b) == typeid(A1))
       {
          return new A1( ((A1)(b)).getWeight() );
       }
    
       else if(typeid(b) == typeid(A2))
       {
          return new A2( ((A2)(b)).getHeight() );
       }
    
       else if(typeid(b) == typeid(B))
       {
          return new B( ((B)(b)).getAge() );
       }
    
       else
          return NULL;
    }
    

    Allerdings finde ich das nicht schön - hat jemand einen hilfreichen Vorschlag für mich? 😞


Log in to reply