delete mit zeiger anderen types?
-
Hallo
ich habe eine abstrakte Basisklasse
struct base { virtual ~base(){} virtual void tu_was() = 0; };sowie ein klassentemplate was davon erbt
template <class T> class derived : public base { public: void tu_was(){} private: T t; };soweit so gut.
jetzt möchte ich eine solche klasse in einer factory erzeugen, ich habe jedoch
nicht T, sondern nur sizeof(T). ich weiß also, wie groß das ganze ding mal wird.die virtuellen funktionen sind auch die selben.
ich dachte mir zuerst, man könnte einfach ein beliebiges derived<ganz_kleiner_typ>
da rein kopieren, um wenigstens die vtbl zu haben. die daten "t" habe ich auch.
somit wäre das objekt komplett konstruiert.nur... wie allokiere ich den speicher? den genauen typ kenne ich nicht, nur die
größe. man könnte jetzt ein char-array allokieren, nur ob das funktioniert?z.b.
base *Construct(unsigned size) { unsigned size_of_vtbl = sizeof(derived<char>) - sizeof(char); base *b = new char[size_of_vtbl + size]; *b = derived<char>(); // vtbl reinkopieren // daten reinkopieren return b; } derived<int> *d = Construct(sizeof(int)); delete d;ich würde gerne ein ganz normales delete verwenden können, um die instanz am
ende zu entsorgen. virtueller dtor sollte ja gesetzt sein.klappt das? gibt es eine methode das ohne gefrickel zu erreichen?
(hintergrund: deserilisation einer klasse).
-
was du var hast ist unfug.
klar kann man ein objekt über einen basisklassen-zeiger löschen, aber du kannst es nicht einfach anlegen, wenn du nur die grösse kennst.
-
wie sollte man den vorgehen, wenn man nur die größen kennt.
ich möchte einen datenstrom deserialisieren, und mehr als die größe hab ich halt
nicht (und die daten).und für jeden kleinen fitzeltyp eine factory erstellen...
das muss doch irgentwie gehen?
-
helferlein schrieb:
wie sollte man den vorgehen, wenn man nur die größen kennt.
ich möchte einen datenstrom deserialisieren, und mehr als die größe hab ich halt
nicht (und die daten).und für jeden kleinen fitzeltyp eine factory erstellen...
das muss doch irgentwie gehen?
Hast beim Speichern vergessen, den Typ mitzugeben. Statt vorher einem 32-Bitter, der die größe angibt, hätte auch ein Byte gereicht, der den Typ angibt, vorausgesetzt, Du hast Dir vorher eine Liste der Typen gemacht, die Du serialisieren können willst.
-
was wieder auf eine factory hinauslaufen würde, wo ich jedes int, jede
struct registireren müsste, oder nochschlimmer hardcoden müsste.für die klassen gibt es schon eine factory, ich versuchs einfach mal damit...
-
helferlein schrieb:
was wieder auf eine factory hinauslaufen würde, wo ich jedes int, jede
struct registireren müsste, oder nochschlimmer hardcoden müsste.Und das manuelle zusammenkopieren von Typen allein anhand ihrer Größe ist kein hardcoden? Und was machst Du wenn mal zwei Deiner Datentypen die gleiche Größe haben?
Nebenbei, Dir ist schon bewußt das die Größe einer Klasse/Struct von den Compilereinstellungen (->byte-alignment) abhängig ist?
-
@volkard: sicher das ein byte reicht für alle typen?

@loks: das alignment ist bei sizeof schon drin. kein problem.
es geht auch nicht darum, anahnd der größe die typen zu erkennen, es geht
nur darum einen spiecherblock dieser größe zu allokieren und diesen dann
als klasse zu interpretieren.würde man über eine weitere indirektionsebene gehen, sprich in der klasse
einen verweis auf den speicher zu habenm würde das gehen, allderdings wäre es
doch schöner, wenn die daten auf dem stack liegen.
-
helferlein schrieb:
@volkard: sicher das ein byte reicht für alle typen?

Eigentlich schon. Weil typischerweise in einer Datei (Dateiabschnitt) eine List(Of Basisklasse) steht. Hab noch keine Basisklasse gesehen, die 256 Blätter in der Klassenhierarchie hat. Basisklassen und members brauchen keine Typkennzeichnung.
-
helferlein schrieb:
@loks: das alignment ist bei sizeof schon drin. kein problem.
Doch, trotzdem ein Problem. Wenn dir Compiler XY 2 Bytes Padding einfügt und Compiler AB 4 zum Beispiel. Oder wenn Fliesskommazahlen (generell Datentypen) anders repräsentiert werden. Oder wenn die Klasse einmal verändert wird. Oder...
Auf jeden Fall: Unterschätze solche implementierungsabhängigen Dinge nicht. Binär serialisieren ist recht heikel und vor allem unportabel.
helferlein schrieb:
es geht auch nicht darum, anahnd der größe die typen zu erkennen, es geht
nur darum einen spiecherblock dieser größe zu allokieren und diesen dann
als klasse zu interpretieren.Du kannst es mit PODs versuchen und
reinterpret_castodermemcpy()einsetzen. Bedenke allerdings, dass hier das Verhalten grundsätzlich undefiniert ist und die kleinste Unstimmigkeit ausreicht, um massive Probleme zu verursachen. Nicht zuletzt wegen der genannten unterschiedlichen Datenrepräsentationen.
-
natürlich. wenn man den compiler wechselt kann alles schief gehen, was
ein gewisses memory-layout erfordert.serialisation ist generell nicht einfach, ich versuche erstmal eine variante
(binär) zum laufen zu bekommen. später wird das ganze um eine textversion
(wird wohl XML werden) erweitert.PODs würden vllt funktionieren, jedoch die ganze schöne objektorientierung
inkl. polymorphie kaputtmachen.habs erstmal mit externen verweisen auf PODs gelöst, hoffe das wird noch

-
helferlein schrieb:
es geht auch nicht darum, anahnd der größe die typen zu erkennen, es geht
nur darum einen spiecherblock dieser größe zu allokieren und diesen dann
als klasse zu interpretieren.das ist totaler blödsinn und wird nicht funktionieren.
nur mit der Größe kann man keine Objekte erzeugen.
Wer ruft denn die Konstuktoren auf?
Das müsste dann der Benutzer ja selbst mittels placement new machen um sein Objekt in den bereitgestelltnen speicher zu bekommen.Was hindert dich daran, eine vernünftige class-Factory zu bauen, die dir nach übergabe eines Schlüssels (string oder int, oder sontst was) ein entsprechende Objekt erzeugt und nen Basisklassenzeiger zurückgibt
edit:
selbst wenn du keine Konstruktoren definiert hast, wird automaitshc einer erzeugt, da bei polymorphen Klassen mindestens die vtables gefüllt werden müssen.
-
vlad_tepesch schrieb:
das ist totaler blödsinn und wird nicht funktionieren.
nur mit der Größe kann man keine Objekte erzeugen.deswegen frag ich ja wie das anders gemacht werden kann.
factorys sind natürlich möglich, nur sehr aufwändig. wenn ich
jeden typ, int, float, structs (jede verschiedene struct)registrieren müsste,
dann gute nacht. user-defined klassen müssen per factory erzeugt werden, aber
bei PODs sollte das doch zu vermeiden sein?!die bisherige lösung besteht wie bereits gesgt aus einem exteren speicher.
damit kann ich immer dieselbe POD-verwaltungsklasse einsetzen.
-
helferlein schrieb:
aber bei PODs sollte das doch zu vermeiden sein?!
Kommt darauf an, was du dir vorstellst. Wie willst z.B. einen 32bit-integer von einem 32bit-float unterscheiden?
-
überhaupt nicht.
class test { public: float f; int i; }; // ...serialisieren test *t = dynamic_cast<test *>(Deserialise(stream)); // dyn_cast nur als beispielda es klassenfactorys gibt, muss ich nur dafür sorgen, dass die daten am richtigen
platz sind. nach der deserialisierung kann ich die erstellte klasse ganz normal
verwenden.
-
Wie behandelst Du einen std::string?
-
das ganze ist zum scheitern verurteilt.
Wie schon gesagt wurde:
die serialisierung des Speichers eines gesammten Objektes macht keinen Sinn.
Ein mal neu kompilieren mit wenigen Änderungen und du kannst alle alten dateien wegschmeißen.
Ganz zu schweigen von einer Compileränderung.bei deiner Methode die Daten aus dem Stream zu holen: woher weißt du wie groß das objekt ist?
Was machst du mit T, die nicht nur Kompositionen aus Elementardatentypen sind und zB selbst speicher anfordern?
-
vlad_tepesch schrieb:
Ein mal neu kompilieren mit wenigen Änderungen und du kannst alle alten dateien wegschmeißen.
Ganz zu schweigen von einer Compileränderung.natürlich. aber sobald die serialisierung fertig ist, muss sie nicht mehr geändert
werden. der rest kann sich ruhig ändern.die größe wird im stream mitgespeichert.
einen std::string sowie bestimmte typen bekommen eine eigene behandlung.
-
helferlein schrieb:
einen std::string sowie bestimmte typen bekommen eine eigene behandlung.
Und damit auch alle Typen, die einen std::string enthalten oder einen std::vector oder einen Typ mit Pimpl-Idiom. Mhhm. Nun wird auch nachträglich gepimlt.
-
volkard schrieb:
Und damit auch alle Typen, die einen std::string enthalten oder einen std::vector oder einen Typ mit Pimpl-Idiom.
eben nicht.
jedes objekt serialisiert nur seine kinder, und auch nur die, die von einer
gewissen klasse erben. das ganze lässt sich bis auf PODs runterbringen.z.b.
class test : public Streamable { public: test() : integer(this), name(this) // registrieren { integer = 5; name = "hallo"; } StreamablePOD<int> integer; StreamablePOD<std::string> name; // std::string bekommt ne extrawurst (spezialisierung) }; Streamable::RegisterClass(Allocator<test>()); Stream s; test t; t.Serialize(s); test *pt = dynamic_cast<test *>(s.Deserialize()); assert(pt->integer == 5);soweit funtzt es schon. stream rein und klasse raus.
ist darauf spezialisiert, eine komplette klassenhierachie mit all ihren
verweisen, etc... laden zu können.vector, string und die ganzen std::*'s bekommen ne eigene spezialisierung.
-
helferlein schrieb:
StreamablePOD<std::string> name; // std::string bekommt ne extrawurstAh! Da hast Du also geschummelt. Ja, diesen Schummel mag ich.
Der führt mich zuclass test:public SomeBase { public: test() :integer(5) ,name(hallo) { } test(istream& in): SomeBase(in), integer(in), name(in) { } void save(ofstream& out) { SomeBase::save(out); integer.save(out); name.save(out); } private: StreamablePOD<int> integer; StreamablePOD<std::string> name; }; REGISTERSTREAMABLE(test,SomeBase);Der Leser macht dann
while(in>>name) ziel.push_back(macher[name].machEinen(in));Der Schreiber macht
for each i in quelle datei<<i->getName()<<' '; i->save(datei);oder so.