Ein Element mit verschiedenen Datentypen
-
Hallo,
ich habe ein Objekt mit einem Element "value".
Dieses Element soll aber von unterschiedlichen Datentypen sein können.Also: bool, char, int, double
In C hätte ich, um den Speicher nur einmal zu verwenden, eine Union verwendet:
union v { bool b; char ch; int in; double db; };Meine Klasse sieht vereinfacht so aus:
class item { private: ??? value; // ??? Hier irgendwie die verschiedenen Datentypen angeben public: ??? getValue(void); // Mit dieser Funktion den Wert zurückgeben lassen }Es wäre auch komfortabel, wenn ich einen anderen Variable den item-Wert zuweisen möchte, dieser
Typ aber ein anderer ist, und die getValue Funktion mir immer den passenden Typ zurückgibt (ggf. runden).Wie macht man sowas in C++ am Besten? Das mit der union gefällt mir auch nicht so wirklich gut.
Danke und Gruß
Thomas
-
union wäre wohl eine Möglichkeit dafür (zusammen mit je einem getX, um auf die einzelnen Wert-Varianten zugreifen zu können). Wenn du es professioneller haben willst, kannst du dir mal VARIANT/COleVariant (MFC) oder Boost::any ansehen.
-
Die erste Frage bei solchen Frickeleien: Wofür?
Als Lösung könntest du ein Template benutzen:
template<class ValueT> class Item { ValueT value; };Es wäre auch komfortabel, wenn ich einen anderen Variable den item-Wert zuweisen möchte, dieser
Typ aber ein anderer ist, und die getValue Funktion mir immer den passenden Typ zurückgibt (ggf. runden).Klingt mir auch sehr unsauber. Aber wieder hilft ein Template:
template<class TargetT> TargetT getValue() { return static_cast<TargetT>(value); }
-
Michael E. schrieb:
Die erste Frage bei solchen Frickeleien: Wofür?
Über das Programm sollen Daten aus ein am PC angeschlossenes Gerät gelesen werden.
Welche Daten (Adressangaben) lassen sich über eine Konfigurationsdatei angeben. Nun kann es sein, dass halt Integer oder Float-Werte gelesen werden sollen.
Die gelesenen Messwerte sollen dann in einer Datenbank gespeichert werden, und evtl. auch noch in einem Fenster ausgegeben werden.Weil die Telegramme zur Datenabfrage annähernd identisch sind, möchte ich ein Objekt für einen Messpunkt haben (item), das irgendwie mit den verschiedenen Datentypen klar kommen sollte.
-
Hi,
ih denke mal, Du solltest die Datentypen sauber "marshallen" - also definieren, welches Format, welche Datentypen auf der Leitung haben sollen und dann sauber konvertieren.
Denn Deine Chance, dass über System-/Compiler(versions)-/OS-/...-grenzen hinweg das Datenformat binär identisch ist, ist sehr klein.
Mein Tipp: aus C/C++-Sicht immer nur "rohen Speicher" (unsigned char) austauschen, mit geeigneten Kennzeichnern oder Protokollen versehen und dann geeignet in sprachspezifische Typen wandeln. Mit allem Anderen tust Du Dir keinen Gefallen - erst Recht nicht mit unions - da gibt es zuviel an Padding, Alignment, Wertebereichsvarianz, ...., was Dir über kurz oder lang die Finger und den Hals bricht.Gruß,
Simon2.
-
Simon2 schrieb:
da gibt es zuviel an Padding, Alignment, Wertebereichsvarianz, ....
Irgendwelche Infolinks dazu oder kannst du mal schnell zusammenfassen, was da genau passieren kann ...

-
Hi,
naja - nur mal auf die Schnelle: Compilern ist es lt. C++-Standard freigestellt, wie sie binär die Datentypen darstellen und da kommt es eben, dass verschiedene Compiler(versionen) auf unterschiedlichen Plattformen das unterschiedlich machen:
- "Padding/Alignment": Bestimmte Rechnerarchitekturen greifen auf geradzahlige Adressen schneller zu als auf ungerade. Deswegen fügen Compiler bisweilen "Füllbytes" ein, um das auszunutzen; Beisp.struct A { char a; char b; };Auch wenn das eigentlich nur 2 Byte bräuchte, macht der compiler daraus:
struct A { char a; char _invisible_filler; char b; char _invisible_filler2; };So kann er ein A auf eine gradzahlige Adresse legen, findet dort A::a und auf der nächsten gradzahlige A::b; _invisible_filler2 sorgt dann dafür, dass in einem Array von As wieder das nächste A wieder auf einer gradzahligen Adresse liegt. Auf einer anderen Plattform (oder einem anderen Compiler) können vielleicht andere Kriterien für so ein Padding herangezogen werden, so dass dasselbe struct im Speicher ganz anders aussieht.
(Vereinfacht dargestellt)- "Wertebereiche": Der Standard legt auch nicht fest, wie groß z.B. der Wertebereich eines unsigned ints ist; wenn eine bestimmte Plattform z.B. besonders schnell mit 16Bit-Werten rechnet, kann der Compiler entscheiden, für ein unsigned int 2 Bytes zu verwenden mit einem Wertebereich von 0 bis 0xFFFF; Auf einer anderen Plattform (oder einem anderen Betriebsmodus) ist dagege das Rechnen mit 4Byte-Werten optimal und unser unsigned int bekommt einen Wertebereich von 0 bis 0xFFFFFFFF;
...
Langer Rede kurzer Sinn: Was Compiler aus den Typen machen, kann binär komplett anders aussehen.
Gruß,
Simon2.
-
@Thomas_v2.1: wo genau willst du was genau vereinheitlichen?
Das Auslesen der Daten wirst du wohl kaum vereinheitlichen können, also was dann?
Das speichern in einem File/Table/..., oder das Rumreichen im Programm...?Wenn du die Daten wie du schreibst in eine DB stecken willst, dann musst du die sowieso entweder a) als string (char/varchar) abspeichern oder b) in unterschiedliche Tables stecken. (Oder c) - ganz schlechte Idee - in unterschiedlichen Spalten des selben Tables eintragen.)
Wenn du nur im Programm drinnen mit "Items" arbeiten willst, und möchtest dass man die z.B. rumreichen kann ohne den genauen Typ zu kennen, dann wäre es wohl am einfachsten die auch gleich in einem std::string (evtl. + Typenangabe) rumzureichen.
-
hustbaer schrieb:
Wenn du nur im Programm drinnen mit "Items" arbeiten willst, und möchtest dass man die z.B. rumreichen kann ohne den genauen Typ zu kennen, dann wäre es wohl am einfachsten die auch gleich in einem std::string (evtl. + Typenangabe) rumzureichen.
Ich glaube das mit dem string ist gar keine schlechte Lösung, auch in Punkto Datenbankspeicherung.
Mit meinen Unions könnte ich mir wie Simon2 beschrieb ja doch einige Probleme einhandeln. Auch wenn es natürlich schön speichersparend wär (programmiere sonst mehr auf Mikrocontrollern).Ich hätte mir halt sowas wie eine Operatorüberladung ( z.b. ein >> für die Ausgabe - könnte ich dann ja bei der String-Lösung zur Ausgabe verwenden) für meine getValue/setValue Methoden vorgestellt.
Aber so firm bin ich in C++ auch noch nicht, als dass ich wüsste welche Möglichkeiten mir die Sprache da bietet um so etwas umzusetzen.
-
Thomas_v2.1 schrieb:
Hallo,
ich habe ein Objekt mit einem Element "value".
Dieses Element soll aber von unterschiedlichen Datentypen sein können.Also: bool, char, int, double
In C hätte ich, um den Speicher nur einmal zu verwenden, eine Union verwendet:
Wie macht man sowas in C++ am Besten? Das mit der union gefällt mir auch nicht so wirklich gut.
In C++ programmiert man objektorientiert und in dem Fall vielleicht mit einem Interface. Ein Template erspart redundante Schreibarbeit.
class Element { public: virtual bool GetBool( bool & result ) { return false; } virtual bool GetChar( char & result ) { return false; } virtual bool GetInt( int & result ) { return false; } virtual bool GetDouble( double & result ) { return false; } }; template<class type> class ElementType : public Element { private: type Data; public: type GetValue( void ) { return Data; } bool GetBool( type & result ) { result = Data; return true; } }; Nun könnte man zum Beispiel ElementType<int> spezialisieren, so dass es auch Doubles zurückgeben kann: bool ElementType<int>::GetDouble( double & result ) { result = static_cast<double>( Data ); return true; }Alle Datensätze sind Elemente.
Alle Funktionen sind typsicher - und genau darum geht es doch?Deine GetValue()-Funktion gibt schließlich auch nur "irgendwas" wieder. Sofern Du den Typ kennst, zum Beispiel ElementType<int>, kannst Du die von Dir gewünschte Funktion GetValue() aufrufen - ebenfalls typsicher.
Casting findet so ausschließlich in einem kontrollierten Rahmen statt und nur an einer klar definierten Position und nicht verstreut im Programm. Wenn was nicht fehlerhaft ist, dann weiß man zumindest, wo man suchen muss.
Mein Tipp: Schreibe nichts, was nicht typsicher ist. Es wird dadurch nicht richtiger, die Wahrscheinlichkeit, dass es falsch wird, steigt aber deutlich.
RTTI bietet im Notfall Informationen, um welchen Typ es sich bei einem Element handelt. Das ist aber nicht die Idee von objektorientierter Programmierung. Schaff Dir lieber ein standardisiertes Interface, über dass Du ausschließlich kommunizierst.
-
Schau dir mal boost.variant an.
boost::variant<int, char, double, bool> foo = 0, bar = true; foo = bar; if(boost::get<bool>(foo)){ // wird ausgeführt }Mit Visitors ist es auch möglich ohne if/else Kette vernünftig damit zu arbeiten.
Kannst es natürlich auch alles aufwendig mit einer Klassenhierarchie nachbauen. Wenn gut gemacht, kommst du wahrscheinlich beim gleichen raus.

-
@Simon: Danke
-
Gern geschehen.

Gruß,
Simon2.