Vector mit verschiedenen Datentypen?
-
Hallo zusammen,
ich habe benötige eine Liste, in welche ich drei verschiedene Datentypen speichern muss. In Java hätte ich die drei Datentypen in drei Klassen gekapselt, unter einer Mutterklasse vereint und einen Vector über die Mutterklasse erzeugt.
Laut meinem fantastischen C++ Buch ist dies aber unter C++ nicht möglich, weil man zwar einem Objekt der Subklasse ein Objekt der Mutterklasse zuweisen kann, aber nicht umgekehrt, ohne einen Informationsverlust (nämlich genau die gekapselten eigentlichen Daten) zu erleiden...
Ich hab mir daraufhin eine Lösung mit Pointern gebaut, in der ich die Daten in einer einzelnen Klasse kapsele, welche die Daten in einem void-Pointer und den Typen explizit speichert. Diese Klasse verwende ich zur Konstruktion meines Vektors.
Dieser Ansatz scheint aber nicht zu funktionieren, denn ich bekomme ohne dass ich es nachvollziehen kann, nachdem ich zwei Daten in der Liste gespeichert habe, die Fehlermeldung:
glibc detected [...] corrupted double-linked list
Selbst beim Debuggen kann ich nicht genau nachvollziehn, wo und warum das passiert, es scheint aber in einer der STL-Units zu passieren. Da ich aber davon ausgehe, dass die STL relativ sauber programmiert ist, vermute ich, dass der Fehler irgendwo in meiner Klasse liegt, vllt sollte ich einen Copy-Konstruktor definieren oder sowas?
Hier ein Auszug der Headerdatei, in welcher ich die Klasse definiert hab:
class Param { enum Params { DATE, TEXT, NUMBER, INVALID }; Params type; void *data; public: inline Param(): type(INVALID), data(NULL) { }; inline Param(const boost::gregorian::date &d): type(DATE), data(new boost::gregorian::date) { *(static_cast<boost::gregorian::date*>(data)) = d; }; inline Param(const std::string &s): type(TEXT), data(new std::string) { *(static_cast<std::string*>(data)) = s; }; inline Param(const int &i): type(NUMBER), data(new int) { *(static_cast<int*>(data)) = i; }; inline boost::gregorian::date date() const { if (type == DATE) return *(static_cast<boost::gregorian::date*>(data)); else return boost::gregorian::date(); }; inline std::string text() const { if (type == TEXT) return *(static_cast<std::string*>(data)); else return std::string(""); }; inline int number() const { if (type == NUMBER) return *(static_cast<int*>(data)); else return 0; }; inline bool isDate() const { return type == DATE; }; inline bool isText() const { return type == TEXT; }; inline bool isNumber() const { return type == NUMBER; }; inline ~Param() { if (type == DATE) delete static_cast<boost::gregorian::date*>(data); else if (type == TEXT) delete static_cast<std::string*>(data); else if (type == NUMBER) delete static_cast<int*>(data); }; };Sieht jemand nen Fehler oder hat ne Idee, wie man das ganze besser lösen kann?
-
Du arbeitest mit dynamischen Daten - da brauchst du nicht nur einen Destruktor, sondern auch Copy-Ctor und operator=, sonst fliegt dir das Programm um die Ohren (list<>::insert() legt eine Kopie des übergebenen Wertes an, die irgendwann wieder gelöscht wird - das Default-Verhalten von C++ ist eine flache Kopie, wodurch beim Zerstören der temporären Variablen dein Speicher demontiert wird).
PS: Warum selber schreiben? Schau mal nach Boost::any.
-
Sowas habe ich vermutet, ich war mir nur nicht sicher, welche Konstruktoren und Operatoren ich noch implementieren sollte...
boost::any? *google*
thx btw
-
noNeed 4 aNick schrieb:
Sowas habe ich vermutet, ich war mir nur nicht sicher, welche Konstruktoren und Operatoren ich noch implementieren sollte...
Dann schreib dir die "Rule of Three" ganz fett ins Notizbuch: "Wer einen der 'großen Drei' (Kopier-Konstruktor, Zuweisungsoperator, Destruktor) benötigt, braucht in der Regel alle drei." (es gibt einige Ausnahmen von dieser Regel, aber nicht sehr viele)
boost::any? *google*
Du verwendest doch schon Boost, da mußt du dich nur etwas genauer umsehen (Boost::any ist afaik eine C++ Alternative zu unions).
-
Dein Java-Beispiel funktioniert deshalb nicht, weil es in Java keine Zeiger gibt. Die "Objekte", die du in Java in dem vector speicherst, sind sowas wie Zeiger in C++. Und genauso kannst du das in C++ auch machen: Schreibe eine Oberklasse, sowei abgeleitete Klassen, die deine Daten kapseln. Dann speicherst du in deinem Vector Zeiger auf diese Oberklasse. An die eigentlichen Daten kommst du dann durch Polymorphie und evtl. explizites Downcasting ran.
-
Alles klar.
Ich wollt auch nur kurz die Boost-Docs überfliegen, die liegen zwar auch lokal irgendwo, aber mit google bin ich halt schneller

-
Das sieht alles sehr unschön aus, wegen der ganzen Casterei, und das ist dann auch wegen dem void* nicht mehr typensicher!
Hier würde sich der Einsatz von Templates anbieten.#include <cassert> #include <vector> #include <string> class ValueImplBase { public: virtual ~ValueImplBase() { } virtual ValueImplBase* clone() const = 0; }; template<typename T> class ValueImpl : public ValueImplBase { public: ValueImpl(const T& object) : object_(object) { } ValueImplBase* clone() const { return new ValueImpl<T>(object_); } T& value() { return object_; } private: T object_; }; class Value { public: template<typename T> Value(const T& object) : p_impl_(new ValueImpl<T>(object)) { } Value(const Value& value) : p_impl_(0) { *this = value; } ~Value() { delete p_impl_; } template<typename T> T& value() { ValueImpl<T>* p_casted = dynamic_cast<ValueImpl<T>*>(p_impl_); assert(p_casted); return p_casted->value(); } Value& operator =(const Value& rhs) { delete p_impl_; p_impl_ = rhs.p_impl_ ? rhs.p_impl_->clone() : 0; return *this; } private: ValueImplBase* p_impl_; }; void test() { std::vector<Value> myVector; myVector.push_back(Value(42)); myVector.push_back(Value(std::string("Ich bin ein String!"))); myVector.push_back(Value(42.0)); Value& v0 = myVector[0]; v0.value<int>() += 2; Value& v1 = myVector[1]; v1.value<std::string>() += " Jetzt wird mir auch noch was angehängt!"; Value& v2 = myVector[2]; v2.value<double>() /= 2.0; // !!! Das führt zu einem Fehler, da v0 int enthält und nicht short !!! v0.value<short>()++; }
-
pumuckl schrieb:
Dein Java-Beispiel funktioniert deshalb nicht, weil es in Java keine Zeiger gibt. Die "Objekte", die du in Java in dem vector speicherst, sind sowas wie Zeiger in C++. Und genauso kannst du das in C++ auch machen: Schreibe eine Oberklasse, sowei abgeleitete Klassen, die deine Daten kapseln. Dann speicherst du in deinem Vector Zeiger auf diese Oberklasse. An die eigentlichen Daten kommst du dann durch Polymorphie und evtl. explizites Downcasting ran.
Ja, das hatte ich auch schon überlegt. Schließlich löst Java das intern mehr oder weniger auch so, dass es nur Referenzen auf Objekte hält...
Hab nun eben noch den CpyCtr und den =Op geschrieben, nun klappts wunderbar.
Wenn ich das nächste mal so eine Konstruktion benötige, werd ich aber wohl einfach boost::any benutzen - es reicht, wenn ich einmal das Rad neuerfunden hab
-
Du kannst auch meine "tolle" Lösung nehmen, die ich vorhin extra für dich geschrieben habe, weil mir so langweilig ist ...

-
Warte, hier haste nen Keks

Zugegeben, die ist auch hübsch geworden... Wie isn das mit der Typensicherheit bei mir? Ist das nicht durch die entsprechenden Abfragen sowieso gegeben?
Naja, ich mach jetzt erstmal mittach

-
Ja, bei dir wird so nix schiefgehen, aber wenn du irgendwo nach void* castest, gehen die Typinformationen verloren. So könntest du einen Apfel nach void* casten und dann das void* nach Birne casten, und du hättest aus dem Apfel eine Birne gemacht - wohlgemerkt eine korrupte, kaputte (verfaulte) Birne, die du direkt wegschmeißen kannst.

-
Unter typsicherheit wird normalerweise auch die im COmpiler eingebaute Typsicherheit verstanden und keine selbstgebaute. Natuerlich kann man sich einfach eine handvoll Byte leeren Speicher reservieren und sich auf irgendeine Art merken, dass das was drinsteht als int zu behandeln ist - sich einen int zu reservieren ist aber trotzdem sicherer und einfacher
