Child Klasse in Basis Klasse umwandeln



  • Hallo,
    ich hab ein kleines Problem mit Vererbung. Ich verwende vc++, auch wenn ich nicht weiss ob das einen Unterschied macht. Hier ist der stark vereinfachte Quelltext:

    "Basis.h":

    class Basis
    {
    public:
        void run(void);
    };
    

    "Basis.cpp":

    #include "Basis.h"
    
    void Basis::run()
    {
        //tu etwas 1
    }
    };
    

    "Child.h":

    class Child : Basis
    {
    public:
        void run(void);
    };
    

    "Child.cpp":

    #include "Child.h"
    
    void Child::run()
    {
        //tu etwas 2
    }
    };
    

    "main.cpp":

    #include "Child.h"
    int WinMain(...)
    {
        list<Basis*> liste = list<Basis*>();
        Child* c = &Child();
        liste.push_back((Basis*)c)
        list<Basis*>::iterator it = liste.front();
        *it->run();
    }
    

    Wenn jetzt die methode "run" ausgeführt wird, wird "tu etwas 1" ausgeführt und nicht "tu etwas 2". Wie kann ich das beheben, bzw. ist sowas in C++ überhaupt möglich?

    Ich freue mich auf eure Antworten.
    mfg lonol15



  • Hi,

    schau dir mal virtual member function an. Und bist du dir sicher, dass du list willst?



  • Child* c = &Child();

    dir ist schon bewusst was das bedeutet?

    und nein, es ist nicht möglich (ausser wenn man hackt, das hat aber auch gefährliche nebeneffekte). wozu möchtest du das tun? ein child ist ein child und soll auch so funktionieren wie ein child. wenn du möchtest, dass etwas wie eine basis funktioniert dann mach eine instanz der basis...

    edit: habs falsch rum verstanden.

    ja, ist möglich und üblich indem du die methoden "virtual" machst.



  • Das sieht schwer nach java aus oO

    Achja, und dein Initialisieren des Iterators ist auch falsch. Front liefert das erste Element in der Liste und nicht den ersten Iterator. Dafür ist begin da.



  • Skym0sh0 schrieb:

    Das sieht schwer nach java aus oO

    Achja, und dein Initialisieren des Iterators ist auch falsch. Front liefert das erste Element in der Liste und nicht den ersten Iterator. Dafür ist begin da.

    Mit java liegst du goldrichtig 😉
    Im richtigen Quelltext müsste ich begin benutzt haben.

    Danke für eure schnellen Antworten. Ich probiers dann gleich mal aus.

    mfg lonol15



  • Irgendwie klappt das nicht so ganz:

    Debug error!

    pure virtual function call



  • Rufst du die Methode aus dem Konstruktor der Basisklasse auf?



  • Zeig mal Code.
    Irgendwo hast du eine pure virtual function, die du nicht überschreibst.



  • ich frage aus sicherheit nochmals nach:
    hast du das so in deinem echten code stehen?

    Child* c = &Child();
    

    edit:
    was du tatsächlich suchst ist ein " std::vector<std::unique_ptr<Basis>> " (in c++11), bzw. ein " std::vector<SharedPtr<Basis> > " (in c++03 mit eigenem shared-ptr-container) oder am besten einfach einen " boost::ptr_vector<Basis> ".



  • Ich übersetze das mal von Java nach C++:

    #include <iostream>
    #include <memory>
    #include <vector>
    
    class base
    {
    public:
    	virtual void foo() const
    	{
    		std::cout << "base!\n";
    	}
    };
    
    class derived : public base
    {
    public:
    	void foo() const final override
    	{
    		std::cout << "derived!\n";
    	}
    };
    
    int main()
    {
    	std::vector<std::unique_ptr<base>> bases;
    	bases.push_back(std::unique_ptr<derived>(new derived));
    	// bases.push_back(std::make_unique<derived>()); // <- falls make_unique verfügbar ist
    	bases.front()->foo();
    }
    

    https://ideone.com/mTatfW



  • Warum final?



  • cooky451 schrieb:

    Ich übersetze das mal von Java nach C++: [...]

    Im konkreten Fall braucht man eigentlich gar kein new; ich würd die Objekte direkt in den vector packen und fertig... 😉



  • Die ganzen C++ für Java-Programmierer Tutorials, die ich gefunden habe, waren leider alle ganz großer Müll.
    Ich versuchs mal selbst, obwohl ich von Java nicht so den Plan habe.

    Javas Objekte sind eigentlich Pointer. In Java kann man keine Objekte deklarieren, nur Pointer auf Objekte, die man dann mit new füllt. In C++ kann man Objekte deklarieren.

    list<Basis*> liste;
    

    Das ist schon die Liste. Du muss da nichts initialisieren oder Konstruktor aufrufen oder new benutzen, es wird eine Liste deklariert und dafür brauchst man halt den Konstruktor der automatisch aufgerufen wird.

    Child* c = &Child();
    

    Das ist ein ziemlich seltsames Konstrukt. Es wird ein Child erzeugt, dann davon die Adresse genommen, dann die Adresse c zugewiesen. Das Child selbst verschwindet jedoch. Du hast also einen Zeiger der ins Nirvana zeigt.

    In C++ benutzt man Vererbung wenn man muss, nicht wenn man kann. (Das wird gleich wieder eine riesen Diskussion, aber ich nutze mal meinen Vorsprung :p )
    Wenn du es irgendwie vermeiden kannst lasse das mit der Vererbung sein. In Java mag es guter Stil sein einen Stack von Vector oder so erben zu lassen, in C++ ist es grob falsch. Sieh dir das Kreis-Ellipse-Problem an und Liskov dazu und überlege dir, ob du wirklich unbedingt Vererbung brauchst. Erst wenn du anfängst mit Funktionspointern Polymorphie zu implementieren solltest du Vererbung nutzen. Um zum Stack per Vector zurückzukommen: Natürlich ist es eine gute Idee die Funktionalität des Vectors zu benutzen um den Stack zu implementieren. Der Vector wäre dann ein privater Member von Stack, ohne Vererbung.

    class Child : Basis
    

    Das bedeutet "ein Child ist eine Basis". Das bedeutet überall dort, wo man eine Basis erwartet, kann man auch ein Child übergeben, denn ein Child ist eine Basis.

    liste.push_back((Basis*)c);
    

    c ist ein Child (Pointer). liste erwartet Basis (Pointer) Objekte. Ein Child ist eine Basis. Also kann man direkt ein Child dort benutzen wo eine Basis erwartet wurde:

    liste.push_back(c);
    

    Benutze keine Casts. Die stellen nur den Compiler ruhig, machen aber den Code nicht richtig. Im Moment weiß der Compiler besser wie C++ geht als du, also hör auf ihn und schalte ihn nicht stumm. Wenn es denn sein muss benutze C++ Casts statt den C-Cast. Der C-Cast ist viel mächtiger und kann so ziemlich alles in alles umwandeln. Das willst du nicht, du willst einen schwachen Cast, der nur das umwandelt was du willst.
    const_cast um constness zu ändern. Mit ändern meine ich uminterpretieren. Das bedeutet "Ich weiß, dass da const T steht, aber ich habe beim Interface-Design versagt und kann es nicht mehr fixen, T ist nicht const, vertrau mir". C++ vertraut dir. Wenn du es anlügst explodiert dein Rechner.

    static_cast macht automatische Umwandlungen hin und zurück. Automatische Umwandlungen sind zum Beispiel float nach int oder Child * nach Basis *. Da es automatisch passiert brauchst du es nicht hinschreiben, C++ weiß selber dass Child eine Basis ist, hast du ja geschrieben. Aber es geht auch zurück: Du kannst per static_cast aus einem Basis * ein Child * machen. Das ist nicht mehr so selbstverständlich, Basis muss nicht unbedingt ein Child sein. Du sagst wieder "Vertrau mir, das ist ein Child, wenn nicht, dann darfst du meine Festplatte formatieren". Und C++ macht.

    dynamic_cast kann wie static_cast Basisklassen zu abgeleiteten Klassen casten, prüft aber vorher, ob das auch geht. dynamic_cast<Child *>(Basis 😉 funktioniert, wenn die Basis wirklich ein Child ist, ansonsten gibt er dir einen nullptr. Ist besser als die Festplatte zu formatieren, wenn du denn gegen nullptr prüfst.

    reinterpret_cast tut tolle Sachen. Das bedeutet "Hier sind Bits von einem Typ, tu mal so als wären es Bits von einem anderen Typ". Ist ganz lustig, aber nicht wirklich sinnvoll.

    Der C-Cast probiert alle Casts durch bis einer klappt. Ist nicht so clever. ~(Jaja, dynamic_cast probiert er nicht, und eigentlich probiert er static_cast und reinterpret_cast in Verbindung mit const_cast, aber Wurscht)~

    list<Basis*>::iterator it = liste.front();
    *it->run();
    

    Das sollte so gar nicht kompilieren. liste.front() gibt die eine Referenz auf das erste Element in der Liste, keinen Iterator (wird hier der Iterator aus der Referenz konstruiert?). Vielleicht meintest du begin(). Aber da du das Element eh schon hast, kannst du gleich run aufrufen:

    liste.front()->run();
    

    auto empfehle ich noch nicht, das füllt dir zwar die Typen automatisch aus, aber wenn du nicht weißt welchen Type was hat, dann bringt dir das nichts.

    Ähnlich wie das was cooky schrieb und eigentlich das Wichtigste:
    Benutze niemals new. Benutze niemals Pointer in *-Form. Das führt nur zu schrecklichen Fehlern. Benutze unique_ptr oder shared_ptr mit make_shared oder make_unique. Wenn du kein make_unique hast, dann schreib es ab.



  • hmm



  • Wie hmm?!

    nwp3 hat schon die allgemeine Forumsmeinung ganz gut wiedergegeben. Und recht hat er dabei auch, soweit ich das sehe.

    Wenn Fragen auftreten, dann hau raus...



  • dot schrieb:

    Im konkreten Fall braucht man eigentlich gar kein new; ich würd die Objekte direkt in den vector packen und fertig... 😉

    Ja, aber er wollte ja explizit eine Sammlung von base* haben, also habe ich mal angenommen dass er mehr als eine Ableitung hat und das ganze Konstrukt auch sonst irgendwie Sinn macht, ansonsten bräuchte man ja auch gar keine Vererbung etc.



  • Ja, hier lag der Fehler:

    Child* c = &Child();
    

    Ich hab immer gedacht, dass das Child Objek bestehen bleibt un ich eben einen Zeiger auf es habe.

    Etwas abgewandelt funktioniert es:

    Child c;
    Child* cp = &c;
    

    Danke für eure Antworten ihr habt mir echt weitergeholfen



  • cooky451 schrieb:

    dot schrieb:

    Im konkreten Fall braucht man eigentlich gar kein new; ich würd die Objekte direkt in den vector packen und fertig... 😉

    Ja, aber er wollte ja explizit eine Sammlung von base* haben, also habe ich mal angenommen dass er mehr als eine Ableitung hat und das ganze Konstrukt auch sonst irgendwie Sinn macht, ansonsten bräuchte man ja auch gar keine Vererbung etc.

    Ja, so ist es. Wen es interessiert:
    Die Basis Klasse hat den Namen GameObject. Unterklassen sind dann z.B. Kamera, Spieler, usw.. Diese ganzen Objekte werden dann in einer Liste gespeichert und in GameEngine werden dann in einer Nachrichtenschleife ihre Methoden init(), calculate() und render() ausgeführt.

    P.S.: bin grade am DirectX lernen 😉

    mfg lonol15


Anmelden zum Antworten