Union Verständnisfrage



    1. Ja, geht - du kannst alle primitiven Datentypen* übereinander in die union legen (deren Gesamtgröße ist dann das Maximum der Member-Größen - bei deiner Beispiel-union also sizeof(long)).

    2. Solange du immer mit dem "aktiven" Member (das letzte Element, dem du etwas zugewiesen hast) der union arbeitest, ist der Daten-Inhalt definiert - und da ist auch kein Cast notwendig für den Zugriff.

    Probleme könnte es jedoch machen, wenn du wild zwischen den Membern wechselst:

    myUnion tester;
    tester.my_short = 4711;//genauer 4711h - aber die Umwandlung übernimmt der Compiler für dich
    
    cout<<tester.my_short;//problemlos - liefert 4711
    cout<<tester.my_long;//undefiniert
    short output=tester.my_long;//ebenfalls undefiniert
    
    1. Nein, das geht nicht - außer du spendiest der union entsprechende Umwandlungs-Konstruktoren (ja, in C++ dürfen auch unions Methoden haben).

    * die einzige Bedingung an die Typen ist, daß es PODs sind - d.h. sie dürfen keine selbstdefinierten Konstruktoren, Destruktoren oder Zuweisungsoperatoren und nur POD-Elemente haben.



  • Probleme könnte es jedoch machen, wenn du wild zwischen den Membern wechselst:

    Eigentlich ist das aber einer der features der union.
    Ich setz unions eigentlich ein, um casts zu vermeiden und wenn die daten soweiso in ne eigene variable mit belieben typ muessen.

    Beispiel, du willst nen double in nen long long "interpretieren" also auf byteebene, nicht mit den normalen umwandlungsvorschriften. Wenn man den reinterpretcast vermeiden will, nimmt man halt nen union ...

    Ansonsten kann man unions gut umgehen in dem man direkt auf byteebene arbeitet. Was uebersichtlicher ist, haengt meist vom Anwendungsfall ab.

    Manchmal sind unions schick, manchmal kommt man mit bytearrays und gecasteten daten besser, besonders wenn die daten komplexer sind.

    Ciao ....



  • Eigentlich ist das aber einer der features der union.

    Nein.



  • RHBaum schrieb:

    Probleme könnte es jedoch machen, wenn du wild zwischen den Membern wechselst:

    Eigentlich ist das aber einer der features der union.

    Ist es nicht - was passiert, wenn du auf das "falsche" Element zugreifen willst, ist Glückssache. Der Hauptvorteil der union ist, daß sie kompakter ist.

    Ich setz unions eigentlich ein, um casts zu vermeiden und wenn die daten soweiso in ne eigene variable mit belieben typ muessen.

    Beispiel, du willst nen double in nen long long "interpretieren" also auf byteebene, nicht mit den normalen umwandlungsvorschriften. Wenn man den reinterpretcast vermeiden will, nimmt man halt nen union ...

    Ja - reinterpret_cast<> ist genauso unsicher wie der Weg über die union. Aber solche Umwandlungen auf Bit-Ebene (egal ob über union oder reinterpret_cast<>) solltest du auf Notfälle beschränken.



  • Mein Problem ist eben, dass ich vom "Benutzer" mehrere Daten verschiedener Standarddatentypen übergeben bekomme. Diese will ich in einer Liste bzw. Map verwalten.

    Über Templates dürfte das ja nicht gehen, da hier auch immer nur eine Templateklasse eines Datentyps in die Maps kommen darf.

    Deshalb dachte ich mir ich stecke die nötigen Datentypen in ein Union rein, und übergebe mir gleichzeitig den Typ als Bytestruct mit. So arbeite ich auf dem "richtigen" bzw. "aktiven" Element, und hab eine Möglichkeit alles in einer Map zu verwalten.

    Bin aber für alle Vorschläge offen. Vielleicht seh ich auch gerade den Wald vor lauter Bäumen nicht 😉



  • Tobias W schrieb:

    Deshalb dachte ich mir ich stecke die nötigen Datentypen in ein Union rein, und übergebe mir gleichzeitig den Typ als Bytestruct mit. So arbeite ich auf dem "richtigen" bzw. "aktiven" Element, und hab eine Möglichkeit alles in einer Map zu verwalten.

    Wie gesagt - solange du weißt, was der aktive Member ist, sollte es da keine Probleme geben (aber im Ernstfall meterlange Fallunterscheidungen - und die sind nicht gerade einfach zu warten).

    Wenn du alle ankommenden Daten einheitlich verarbeiten willst, solltest du es mal mit Polymorphie versuchen - eine abstrakte Basisklasse deklariert alle erforderlichen Zugriffsmethoden (pur virtuell), die Datenklassen leiten davon ab und implementieren diese Methoden. Und deine "Liste bzw. Map" speichert Pointer auf diese Elemente.



  • Genau, das wäre die andere Möglichkeit. Dann hab ich im gegensatz zu langen Fallunterscheidungen, x verschiedene Klassen die alle das selbe tun.

    Allerdings hätte man dann auch die Möglichkeit nicht nur Standarddatentypen nutzen zu können sondern, falls zukünftig erforderlich, auch weitere Strukturen und Klassen mit zu verarbeiten...

    Ich glaube ich hab mich entschieden 😃



  • Tobias W schrieb:

    Genau, das wäre die andere Möglichkeit. Dann hab ich im gegensatz zu langen Fallunterscheidungen, x verschiedene Klassen die alle das selbe tun.

    Nicht unbedingt - schließlich unterscheiden sich ja die Daten, mit denen die einzelnen Klassen zu tun haben. Und wenn du doch überall das selbe machen willst, kannst du ja Polymorphie und Templates kombinieren:

    class DataBase
    {
      ...
    }
    
    template<typename T>
    class Data : public DataBase
    {
      ...
    }
    
    std::map<std::string,DataBase*> sorted_data;
    


  • Tobias W schrieb:

    ...vom "Benutzer" mehrere Daten verschiedener Standarddatentypen übergeben bekomme. ...

    Klingt nach "Laufzeit".

    Tobias W schrieb:

    ...Templates ...

    Ist "Compilezeit".
    => Der "Typwahlmechanismus von Templates" ist ungeeignet dafür.
    Aaaaaber: Sie können Dir trotzdem eine Menge Tipparbeit ersparen, wenn es für alle/viele Typen gemeinsame Bearbeitungsmethoden gibt.
    Aber sie kannst Du eben erst da einsetzen, wo Du bereits (über einen anderen Mechanismus) den Typen bestimmt hast.

    Ich würde auch für Laufzeit-Polymorphie via virtual plädieren (und spätestens da kannst Du dann evtl. auch templates einsetzen).

    Gruß,

    Simon2.



  • Du meinst sowas:

    struct AllData 
    {
      char datatype_;
      union {
        int i;
        long l;
        float f;
        bool b;
        char c;
        double d;
        /*...*/
      } data_;
    };
    
    typedef std::deque<AllData> Datalist;
    
    std::ostream& operator<<(std::ostream& os, AllData& const data) {
      switch (data.datatype_) {
        case 'i': os << data.data_.i; break;
        case 'l': os << data.data_.l; break;
        case 'c': os << data.data_.c; break;
        case 'd': os << data.data_.d; break;
        case 'b': os << data.data_.b; break;
        /* ect... */
      }
      return os;
    }
    

    Das kann man so machen, oder man kanns ueber polymorphie machen:

    class basic_data 
    {
       /* ... */
    private:
      friend std::ostream& operator<< (std::ostream& os, basic_data& const data) {
        return data.print(os);
      }
      virtual std::ostream& print(std::ostream& os) const = 0;
    };
    
    template <typename T> 
    class dataholder : public basic_data 
    {
      T data;
    private:
      std::ostream& print(std::ostream& os) {
        os << data; return os;
      }
    }
    
    typedef std::deque<basic_data*> Datalist;
    

    Bei der Polymorphie kann man natuerlich nichtmehr die Objekte selbst in die Container packen sonder nur Zeiger darauf.

    /edit: natuerlich wieder zu lang gebraucht 🙄

    /edit2: ja, templates sind compiletime abhaengig. Allerdings sind die switch-cases das auch, die man nutzt, um in der union-variante auf die Daten zuzugreifen, bzw. die man nutzen muss, um in der polymorphen variante via dynamic_cast an die Daten zu kommen. Folgende Ergaenzungen (nicht getestet und evtl nicht ganz ausgereift) sind mir noch zur template-polymorphen variante eingefallen:

    class basic_data 
    {
      /*...*/
      template<typename T>
      static basic_data* wrap_data(T& const t) {
        return new dataholder<T> (t);  //entsprechender Ctor fuer dataholder ist trivial
      }
    
      template<typename T>
      T* unwrap_data() {
        dataholder<T>* pt = dynamic_cast<dataholder<T>*> (this);
        return pt ? &(pt->data) : NULL;
      }
    
    };
    
    /edit3: ich weiss, man kann die methoden nicht alle innerhalb der Klassendefinition definieren weil zykl;ische Abhaengigkeiten bestehen, deshalb muss die iMplementierung hintenangestellt werden. und der Zugriff &(pt->data ) sollte evtl auch durhc ne methode von dataholder gekapselt werden, falls da was spezialisiert werden soll...
    


  • @ CStoll & pumuckl:
    Hm, ich hätte jetzt eiskalt behauptet das man eine Templateklasse nicht von einer "normalen" ableiten kann. Ich hatte in der Richtung nämlich mal Probleme die sich alle aufgelöst haben, als ich die Basisklasse auch zur Templateklasse gemacht habe.

    Muss ich gleich mal ausprobieren...

    Edit:
    Geht, komisch, was hab ich damals nur gemacht?



  • Klar kannst du eine Template-Klasse von einer "reinen" Klasse ableiten (siehe zum Beispiel in der Standard-Bibliothek ios_base und basic_ios<>). Nur mußt du bei den geerbten Methoden die korrekte Signatur beachten (kannst also keine Typen verwenden, die auf dem Template-Parameter basieren).



  • Ok, nur bin ich jetzt bei dieser Methode auf einen weiteren Stolperstein gestoßen:

    Meine abstrakte BaseClass soll die DerivedClass "zwingen" den Operator kleiner (<) und größer (>) zu implementieren. Doch die DerivedClass ist eine Templateklasse, und in der BaseClass habe ich somit den typename T der in den Operatoren übergeben wird nicht.

    Um mich verständlicher auszudrücken, hier ein Beispiel:

    class BaseClass
    {
    public:
      virtual bool operator<(const /*???*/& parameter) const =0;
    };
    
    template <class T>
    class DerivedClass : public BaseClass
    {
    public:
      bool operator<(const T& parameter) const;
    };
    

    Ich hab meine Frage im Quellcode mit ??? dokumentiert. Mir fehlt dort ja der Typ des Parameters, weil die BaseClass kein Template ist (und auch nicht sein soll!). Ich will nur sichergehen, dass die abgeleitete Klasse den Operator implementiert.

    Hat wer eine Idee? 😕



  • Afaik kannst du die abgeleiteten Klassen fast zu nichts "zwingen" - außer daß sie die abstrakten Methoden implementieren müssen. (aber bei den abstrakten Methoden sind die abgeleiteten Klassen an das Interface der Basisklasse gebunden). d.h. du könntest höchstens den Vergleichsoperator abstakt definieren (bzw. eine abstrakte Hilfsfunktion nutzen), damit ihn alle abgeleiteten Klassen umsetzen müssen:

    class base
    {
    public:
      virtual bool operator<(const base& other) const = 0;
    };
    
    template<typename T>
    class derived
    {
    public:
      derived(const T& value);
      virtual bool operator<(const base& other) const
      {
        derived* theOther = dynamic_cast<derived*>(&other);
        if(theOther==NULL)
          return false;
        //vergleiche
      }
    };
    

    (wobei - ich würde eher eine Hilfsmethode verwenden, die von den verschiedenen Vergleichsoperatoren genutzt wird)



  • Gute Idee, aber schön wäre es natürlich ohne Umstände (also ohne dynamic_cast).

    Außerdem ist mir gerade aufgefallen das ich da viel zu kompliziert rangehe:
    Im Prinzip will ich nur feststellen, ob der "Value" in den Abgeleiteten Klassen in gewissen Grenzen liegt. Diese Grenzen stehen auch in der abgeleiteten Klasse. Also muss ich ja nicht unbedingt die Operatoren überladen, sondern schreibe eine extra Funktion dafür die jeder implementieren kann wie er will.

    Also wie du schon sagst: eine Hilfsfunktion (was hier auch viel sinnvoller ist 🙄 ). Vielen Dank, damit hat sich das Problem erledigt...


Anmelden zum Antworten