Vector für unterschiedliche Datentypen



  • Hallo zusammen,

    bin neu hier und auch neu in der c++ - Welt. D.h. ich habe bereits ganze 2 Monate "Erfahrung" angesammelt. Also nicht zu böse mit mir sein, wenn mein Code mal unsauber, meine Fragen mal "blöd" sind o.ä. 😉

    Aber nun zum Problem:
    Vectoren in c++ sind ja was feines, nur hat mich bei einer Aufgabenstellung gestört, dass sie nur Elemente eines Typs beinhalten können. Ein wenig herumgegoogelt habe ich auch und festgestellt, dass es dafür wohl auch keine ganz einfache Lösung gibt - wohl aber auch, dass es in Boost einen Container gibt, der das kann.
    Ich möchte mich nichtsdestotrotz selbst an einer eigenen Klasse multivector versuchen, die die Datentypen int, long, float, double und long double aufnehmen kann (man will ja selbst was dazulernen und nicht nur anwenden).

    Erst mal der Code:

    #include <vector>
    
    using namespace std;
    
    class multivector{
    public:
      //Konstruktoren:
      multivector(){             //mit "unbegrenzter" Größe
        counter = 0;
        limit = false;
      }
      multivector(int max_size){ //mit Größe max_size
        counter = 0;
        limit = true;
    
        datatype.resize(max_size);
        int_vector.resize(max_size);
        long_vector.resize(max_size);
        float_vector.resize(max_size);
        double_vector.resize(max_size);
        long_double_vector.resize(max_size);
      } 
    
      //push_backs für die einzelnen Fälle
      void push_back(int value){
        if (!limit)
          int_vector.resize(counter + 1); //speicher reservieren für "unbegr." Größe
        int_vector.at(counter) = value;
        counter++;
        datatype.push_back(1);
      }
      void push_back(long value){
        if (!limit)
          long_vector.resize(counter + 1);
        long_vector.at(counter) = value;
        counter++;
        datatype.push_back(2);
      }
      void push_back(float value){
        if (!limit)
          float_vector.resize(counter + 1);
        float_vector.at(counter) = value;
        counter++;
        datatype.push_back(3);
      }
      void push_back(double value){
        if (!limit)
          double_vector.resize(counter + 1);
        double_vector.at(counter) = value;
        counter++;
        datatype.push_back(4);
      }
      void push_back(long double value){
        if (!limit)
          long_double_vector.resize(counter + 1);
        long_double_vector.at(counter) = value;
        counter++;
        datatype.push_back(5);
      }
    
      /*Für die Ausgabe sowas in der Art: 
        int at (int position){
          if (datatype[position] == 1)
            return int_vector[position];
          else if (datatype[position] == 2)
            return long_vector[position];
          etc...
        }
      */
    
      int size(){
        return counter;
      }
    
      bool empty(){
        if (counter == 0) return true;
        else return false;
      }
    
    private:
      int counter; //für size()
      vector<int> datatype;
      bool limit; //für Konstruktoren. unbeschränkt viele Elemente oder nicht?
    
      //vectoren für die einzelne fälle
      vector<int> int_vector;
      vector<long> long_vector;
      vector<float> float_vector;
      vector<double> double_vector;
      vector<long double> long_double_vector;
    };
    

    Ganz grob und umgangssprachlich ist das ein Vector, der wiederum aus Vectoren besteht, welche jeweils die geforderten Datentypen aufnehmen können. Jeder übergebene Datentyp wird in den entsprechenden Vector geschrieben. Im int-vector datatype stehen Ganzzahlen von 1 bis 5 (stellvertretend für: von int bis long double), womit man (so weit mein Gedankengang) später jeder Position im multivector einen Typ zuordnen kann.
    Jetzt der Knackpunkt: Die Methode "int at (int position)" (auskommentiert), welche sozusagen der Methode "at" vom normalen vector entsprechen soll. Problem: Sie gibt nunmal einen int zurück. Soll aber abhängig vom Wert in datatype einen entsprechenden Typ zurückgeben (wie im Methodenkörper angedeutet).
    Stellt sich die Frage: Kann man entweder
    1. einer Methode mehrere Rückgabetypen "zur Auswahl" stellen (ich fürchte nicht)
    oder
    2. mehrere im Grunde gleiche Methoden (aber verschiedenen Typs) definieren, welche mit if verzweigt werden?

    Oder habt ihr eine ganz andere Idee? Freu mich über jede Anregung 🙂



  • g0nz0 schrieb:

    Oder habt ihr eine ganz andere Idee? Freu mich über jede Anregung 🙂

    Interessant ist doch, dass der Wunsch, mehrere grundsätzlich unabhängige Typen in einen Container (oder eine Variable) zu stecken, hier ab und zu vorkommt – und zwar vor allem von Leuten, die sich noch nicht allzu lange mit C++ beschäftigen. 🙂

    Darum unterstelle ich dir, dass du das mehr als Experiment durchführen willst und nicht wirklich eine konkrete Problemstellung dahinter steckt. Kann das sein? Denn das Gemeinsam-Verwalten mag zwar nützlich klingen, bringt jedoch in der Praxis einige Probleme mit sich und ist oft gar nicht sinnvoll. Zumindest nicht in der Weise, wie es sich Anfänger häufig vorstellen.

    Wie willst du z.B. auf die unterschiedlichen Typen reagieren? Manuell mit if oder switch Fälle unterscheiden ist nicht empfehlenswert. Denn du speicherst ja die Typen gemeinsam ab, weil sie etwas gemeinsam haben. Also solltest du die Gemeinsamkeit irgendwie nutzen können. Wenn du eine Abstraktion aufbauen kannst, die intern für jeden Typen eine semantisch ähnliche Operation durchführt, aber von aussen gleich aussehen soll, kannst du sogar ein C++-Sprachmittel namens Laufzeitpolymorphie einsetzen. Dafür solltest du dich aber ausführlich mit Klassen beschäftigt haben.

    Aber wie schon erwähnt bleibt vor allem die Frage, wozu du das Ganze benötigst. 😉



  • Frage: Weiss der Benutzer deiner Klasse, welchen Rückgabewert er bekommt?
    Die Syntax

    int i    = mvec.at<int   >(31);
    float f  = mvec.at<float >(41);
    double d = mvec.at<double>(59);
    

    bekommst du recht einfach hin. In dem Fall brauchst du auch nur einen Vektor.

    Wenn der Nutzer allerdings nicht weiss, was er für einen Rückgabewert er bekommt hast du ein Problem. Denn diesen müsste er erst einmal herausfinden und dafür benötigst du zusätzlich Informationen über den Typen eines Elements an einer bestimmten Position (möglich mit zwei Vektoren).



  • Nein nein, da steht nichts konkretes dahinter. Experiment triffts ganz gut. Ich bin im Moment in der Phase, alles mögliche mal ausprobieren zu wollen. Ich weiß ehrlich gesagt schon gar nicht mehr, welche Aufgabe ich gerne mit soetwas lösen wollte. Aber wie gesagt - ich möchte neben den Hausaufgaben im Studium gelegentlich auch "eigene" Sachen ausprobieren (auch wenn ich keinen konkreten Nutzen daraus ziehe).

    @MulpaRadig:
    Nein, das würde der Benutzer nicht wissen. Aber im vektor datatype stehen ja Zahlen von 1-5 (1 für int, 5 für long double). D.h. wenn ich herausfinden will, was z.B. in multivector[5] für ein Typ steckt, gucke ich in datatype[5]. Und je nach Wert, weiß ich schon bescheid.



  • du bräuchtest dann noch mindestens eine struktur die speichert wann was wo abgelegt wurde und eine Möglichkeit das zurückzugeben.

    Es wäre einfacher einen "multidatenhalter" zu bauen und den dann in den Vek zu stecken, skizziert etwa so:

    class MuliD 
    {
        bool m_nofrac;
        long double m_double;
        long long int m_int;
    public:
        MultiD(long double ld)  :  m_double(ld), m_nofrac(false) {}
        MultiD(double d)        :  m_double(d),  m_nofrac(false) {}
        MultiD(float f)         :  m_double(f),  m_nofrac(false) {}
    
        MultiD(unsigned int ui) :  m_int(ui),    m_nofrac(true)  {}
        MultiD(int i)           :  m_int(i),     m_nofrac(true)  {}
        MultiD(bool b)          :  m_int(b),     m_nofrac(true)  {}
    
        bool isInt() { return m_nofrac; }
        bool isDouble() { return !m_nofrac; }
    
        long double asDouble() { if (!m_nofrac) return m_double; return (long double(m_int)); }
        long int asInt()    { if (!m_nofrac) return (long long int(m_double)); return m_int; }
    }
    

    oder so: http://doc.qt.nokia.com/4.6/qvariant.html

    Insgesamt ist sowas im Eigenbau aber müssig ...



  • Ich finde die Implementation von Boost.Variant deutlich besser als die von Qt.

    Aber stimmt schon; entweder läuft das Problem auf etwas in der Art von std::vector<boost::variant> oder auf einen variant_vector<> (der die Funktionalität von Variant gleich selber implementiert und deshalb performanter ist) heraus. Das Thema ist aber sehr komplex...

    @g0nz0: Ich habe mich eher gefragt, wie der Benutzer denn den Container aufrufen sollte. Irgendwohin muss er ja das Element speichern und daher muss er ja eigentlich im Augenblick der Zuweisung wissen, welchen Typ er hat. In C++ herrscht starke Typisierung, daher geht so etwas wie

    var x = mvec.at(5); // var ist entweder int oder double oder long oder ...
    

    nicht.



  • Boost.Any wäre hier auch noch zu nennen.



  • Damit der Unterschied klar wird:

    • Boost.Any kann beliebige Typen speichern (im Prinzip ein typsicherer void* ).
    • Boost.Variant kann Typen aus einer vorgegebenen Menge speichern (also eine Art moderne union ).

Log in to reply