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_cast oder memcpy() 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.


  • Mod

    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 beispiel
    

    da 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 extrawurst
    

    Ah! Da hast Du also geschummelt. Ja, diesen Schummel mag ich.
    Der führt mich zu

    class 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.


Anmelden zum Antworten