Mehrfachverebung (mal wieder)



  • Hallo,
    ich hab schon nach dem Thema gesucht aber nix geeignetes gefunden. Die Leute haben größere Probleme mit Mehrfachvererbung als ich... ich versteh da nämlich was grundlegen nicht. Ich will Klassen von verschiedenen Basisklassen erben lassen. Das funktioniert soweit, der Compiler meckert nicht und warnt auch nicht. Nur die Ausgabe ist Blödsinn, bez. das Programm stürzt ab. Ich hab hier eigentlich einen ganz einfachen Code. Vielleicht seh ich den Wald vor lauter Bäumen nicht, ich weiß nicht was da schief läuft:

    class Meta
    {
    	public:
    		unsigned int zahl;
    };
    class Klasse1
    {
    	public:
    		Klasse1(){};
    		~Klasse1(){};
    };
    class Klasse2
    {
    	public:
    		Klasse2(){};
    		~Klasse2(){};
    };
    class Erber1 : virtual public Meta, public Klasse1
    {
    	public:
    		Erber1()
    		{
    			zahl = 1;
    		};
    		~Erber1(){}; 
    };
    class Erber2 : virtual public Meta, public Klasse1
    {
    	public:
    		Erber2()
    		{
    			zahl = 2;
    		};
    		~Erber2(){};
    };
    
    int main()
    {
    	Erber1* erber1 = new Erber1();
    	Erber2* erber2 = new Erber2();
    
    	printf("Erber1: %s\n",erber1->zahl);
    	printf("Erber2: %s\n",erber2->zahl);
    }
    

    So, das Programm stürzt ab, der Debugger zeigt auf das zweite "printf", also ist im ersten "printf" wohl was schief gelaufen. Ich hätte jetzt eigentlich erwartet dass das Programm durchläuft und mit die Zahlen "1" und "2" ausgibt.

    Ich will nur auf das Attibut "zahl" zugreifen, welches doch alle Klassen besitzen müssten, oder?!?

    Weiß mir jemand was da nicht stimmt?
    MfG



  • Ganz einfach.
    1. Du benutzt C++, also nutze auch die passenden Funktionen.
    2. Du schreibst

    printf("Erber1: %s\n",erber1->zahl);
    

    Du hast ein %s in deinem printf, womit printf erwartet, dass an dieser Stelle ein string sein sollte. erber1->zahl ist aber kein string, von daher error oder warning.
    Mach aus dem %s ein %d, oder und das ist die bessere Variante, benutz std::cout

    std::cout << "Erber1: " << erber1->zahl << std::endl;
    std::cout << "Erber2: " << erber2->zahl;
    

    Für std::cout muss iostream verwendet werden (#include <iostream>).
    Und schon funktioniert dein Programm.



  • Hmmm ok, hast Recht. Wie peinlich... Vielen Dank soweit mal.

    Wenn das also funktioniert, dann würd ich gern mal mein eigentliches Problem präsentieren. Ich will einen Baum implementieren und ein Visitor-Pattern dazu. Aber da klappt der Zugriff auf das gemeinsame Attribut nicht mehr.

    Why?

    #include <iostream>
    class Meta
    {
    	public:
    		unsigned int art;
    };
    class Knoten
    {
    
    };
    class Gruppe : public Knoten
    {
    	public:
    		unsigned int getAnzahl()
    		{
    			return kinder.size();
    		};
    		void addChild(Knoten* kind)
    		{
    			kinder.push_back(kind);
    		};
    		Knoten* getChild(unsigned int index)
    		{
    			return kinder[index];
    		};
    		std::vector<Knoten*> kinder;
    };
    class MeinKnoten : public Knoten, public Meta
    {
    	public: 
    		MeinKnoten(char* name)
    		{
    			art = 0;
    			sprintf_s(mName,sizeof(mName),"%s",name);
    		};
    		~MeinKnoten()
    		{
    
    		};
    		unsigned int getArt()
    		{
    			return art;
    		};
    		char mName[1024];
    };
    class MeineGruppe : public Gruppe, public Meta
    {
    	public: 
    		MeineGruppe(char* name)
    		{
    			art = 1;
    			sprintf_s(mName,sizeof(mName),"%s",name);
    		};
    		~MeineGruppe()
    		{
    
    		};
    
    		char mName[1024];
    };
    class Besucher
    {
    	public: 
    
    		Besucher()
    		{
    
    		};
    		~Besucher()
    		{
    
    		};
    
    		void apply(MeinKnoten& myNode)
    		{
    			std::cout << "MeinKnoten:: Name:" << myNode.mName << "Art:" << myNode.art << std::endl;
    		};
    		void apply(MeineGruppe& myGroup)
    		{
    			std::cout << "MeineGruppe:: Name:" << myGroup.mName << "Art:" << myGroup.art << std::endl;
    
    			for(unsigned int x=0;x<myGroup.getAnzahl();x++)
    			{
    				std::cout << "Kind:: Art:" <<((Meta*)myGroup.getChild(x))->art << std::endl;
    			}
    		};
    };
    int main()
    {
    	MeineGruppe* welt = new MeineGruppe("Welt");	
    	MeineGruppe* vater = new MeineGruppe("Vater");
    	MeinKnoten* kind = new MeinKnoten("Kind");
    
    	welt->addChild(vater);
    	vater->addChild(kind);
    
    	Besucher* besucher = new Besucher();
    	besucher->apply(*welt);
    }
    

    Mein Programm liefert folgende Augabe:

    MeineGruppe:: Name:WeltArt:1
    Kind:: Art:4615400
    

    Ich hätte jetzt als zweite Ausgabezeile "Kind:: Art:1" erwartet... 😞



  • Wenn du einen C-Style-Cast verwendest, musst du dich auch nicht über solche Probleme wundern. Mit dynamic_cast wird es funktionieren.
    Dazu müssen Meta und Knoten natürlich virtuelle Funktionen haben (das könnten die Destruktoren sein).

    Von std::string hast du schon mal etwas gehört? So was

    sprintf_s(mName,sizeof(mName),"%s",name);
    

    in einem C++-Programm ist ja grauenvoll ...



  • Wahrscheinlich mal wieder ein 60 jähriger "C/C++" Prof im Spiel 😃



  • Außerdem heißt es 'T* ptr = new T;' und du gibts deinen Speicher nirgens frei 😉



  • Danke für die Antworten erst mal.

    @314159265358979: Keine Sorge, ansonsten gebe ich meine Speicher schon wieder frei. Das war nur ein Beispielprogramm.

    @manni66: Ich weiß dass ich das mit dynamic_cast lösen könnte. Aber gehts denn gar nicht ohne das? Mit gehts dabei ums Verständnis. Was ist wenn ich eine sehr breite Vererbungshierarchie habe? Dann liefern alle dynamic_cast einen Nullpointer, bis auf einen.



  • SchlechterInformatiker schrieb:

    @manni66: Ich weiß dass ich das mit dynamic_cast lösen könnte. Aber gehts denn gar nicht ohne das? Mit gehts dabei ums Verständnis. Was ist wenn ich eine sehr breite Vererbungshierarchie habe? Dann liefern alle dynamic_cast einen Nullpointer, bis auf einen.

    Ich verstehe nicht, was du meinst. Wenn dein tatsächliches Objekt A ein B ist, kannst du es mit dynamic_cast sicher nach B casten. Wenn A kein B ist, kannst du es mit keinem anderen cast-Typ einfach ein A zu einem B verwandeln. Wenn alle Voraussetzungen für einen dynamic_cast gegeben sind, verhält sich ein C-Style-Cast wie ein dynamic_cast, andernfalls erhält man möglicherweise Müll, wie in deinem Fall.



  • Mit einer "breiten Vererbungshierarchie" meine ich dass sehr viele Klassen von "Gruppe" erben. Also "Klasse1" erbt von "Gruppe","Klasse2" erbt von "Gruppe","Klasse3" erbt von "Gruppe" usw... In einer Liste werden trotzdem nur allgemein "Knoten" gespeichert, ich will aber wissen was es genau ist, wenn ich ein Objekt aus der Liste herausnehme.

    Eine lange Vererbungshierarchie wäre demnach folgendes: "Klasse1" erbt von "Gruppe", "Klasse2" erbt von "Klasse1", "Klasse3" erbt von "Klasse2" usw...

    Die Klasse "MeineGruppe" hat eine Liste, in der sich Zeiger auf sowohl Instanzen der Klasse "MeineGruppe" als auch der Klasse "MeinKnoten" befinden und vielleicht später noch andere. Alles Erben der Klasse "Knoten". Also gebe ich für die Liste an, dass sie Zeiger auf die gemeinsame Basisklasse von "MeineGruppe" und "MeinKnoten", nämlich "Knoten" speichern soll:

    std::vector<Knoten*> kinder;
    

    Wenn ich dann später Elemente aus der Liste herausnehmen will erhalte ich natürlich Zeiger auf Instanzen der Klasse "Knoten", tatsächlich verbirgt sich hinter dem zeiger aber eine "MeineGruppe"-Instanz oder eine "MeinKnoten"-Instanz und ich möchte wissen um was es sich dabei nun wirklich handelt.

    Also kann ich hergehen, und den Knoten* mit dynamic_cast in einen MeineGruppe* zu verwandeln. Danach kann ich überprüfen ob der erhaltene Zeiger NULL ist oder nicht. Wenn der erhaltene Zeiger nicht NULL ist, dann ist eine "MeineGruppe"-Instanz hinter dem Zeiger.

    Wenn er aber NULL ist, ist keine "MeineGruppe"-Instanz hinter dem Zeiger. Dann kann ich hergehen und versuchen, den Knoten* in einen MeinKnoten* zu verwandelt und wieder auf NULL überprüfen.

    Ich suche jetzt im Prinzip sowas wie ein "instanceof" aus Java...
    Ich hab im Internet gesucht und da eben solche Lösungen mit dynamic_cast gefunden. Und bei einer recht ungeschickten, breiten Vererbungshierarchie, wo viele Klassen von "Knoten" erben, würde das zu sehr vielen dynamic_casts führen, von denen immer alle bis auf eines einen NULL-Pointer liefern weil der dynamic_cast eben fehlgeschlagen ist.

    Daher dachte ich mir, ich lasse die Klassen, die von "Knoten" erben, zusätlich noch von einer Klasse "Meta" erben, die festhält, um was für eine Art Knoten es sich handelt. Am besten mit einem unsigned int oder so, dann kann ich mit einem switch entscheiden um was es sich letzendlich für eine Instanz hinter dem Zeiger handelt.

    Das Problem ist, die Klasse "Knoten" und "Gruppe" sind mir vorgegeben und ich weiß dass "Gruppe" schon von "Knoten" erbt. Ich kann also nicht einfach der Klasse "Knoten" ein Attribut verpassen mit dem ich entscheiden kann um was es sich dabei tatsächlich handelt. Und zusätlich muss ich zwei eigene Klassen implementieren von der eine von "Knoten" und die andere von "Gruppe" erbt. Die Funktionalität mit dem "getChild()" von "Gruppe" ist eben vorgegeben, und die liefert nun mal nur "Knoten", auch wenn es sich dabei tatsächlich sogar um eine "Gruppe" oder sogar um ein "MeinKnoten" oder ein "MeineGruppe" handeln würde.

    Also das mit dem dynamic_cast funktioniert, war sowieso meine erster Ansatz, nur denke ich mir eben dass es bei einer breiten Vererbungshierarchie eben ineffizient wird wenn ich sehr viele dynamic_casts machen muss und hinterher auf NULL abgleichen muss.



  • Ich glaube, du hast nicht wirklich verstanden, was ich gemeint habe. Wenn du in dem von dir geposteten Code den Cast (Meta*) durch einen dynamic_cast<Meta*> ersetzt und den Klassen Meta und Knoten einen virtuellen Detruktor verpasst, funktioniert dein Code, d.h. Du bekommst die von dir gewünschte Ausgae 1.



  • SchlechterInformatiker schrieb:

    Also kann ich hergehen, und den Knoten* mit dynamic_cast in einen MeineGruppe* zu verwandeln. Danach kann ich überprüfen ob der erhaltene Zeiger NULL ist oder nicht. Wenn der erhaltene Zeiger nicht NULL ist, dann ist eine "MeineGruppe"-Instanz hinter dem Zeiger.

    Wenn dynamic_cast fehlschlägt, wird eine Exception geworfen. Korrektur es bleibt beim Null-Zeiger.

    SchlechterInformatiker schrieb:

    Wenn er aber NULL ist, ist keine "MeineGruppe"-Instanz hinter dem Zeiger. Dann kann ich hergehen und versuchen, den Knoten* in einen MeinKnoten* zu verwandelt und wieder auf NULL überprüfen.

    Was willst Du erreichen? Was Du gerade machst ist ziemliches Gewürge.

    SchlechterInformatiker schrieb:

    Ich suche jetzt im Prinzip sowas wie ein "instanceof" aus Java...

    Hier willst Du eindeutig Dispatching nachbauen, sofern dies nicht für dynamic Dispatching gedacht ist, hat die Klassenhierachie eindeutig einen heftigen Designfehler.



  • @~john das mit der Exception stimmt so nicht - zumindest nicht, wenn er wie er dauernd sagt mit pointern arbeitet, der ist dann einfach 0 (s.Api) nur bei Referenzen wird eine geworfen.

    http://www.cplusplus.com/doc/tutorial/typecasting/ schrieb:

    When dynamic_cast cannot cast a pointer because it is not a complete object of the required class -as in the second conversion in the previous example- it returns a null pointer to indicate the failure. If dynamic_cast is used to convert to a reference type and the conversion is not possible, an exception of type bad_cast is thrown instead

    Was unterscheidet denn deine KindKlassen von der ElternKlasse? Zusätzliche Member? Neue Methoden? Selbe Methoden nur mit anderen Inhalten? Solange du die Methoden die du nutzen willst in der ElternKlasse mit virtual deklarierst und mit der selben Signatur in den Kindern neu implementierst, brauchst du kein dynamic_cast von der ElternKlasse auf die KindKlasse - das virtual sorgt dafür das die richtige Funktion (die der Kinder) aufgerufen wird auch wenn du mit einem Pointer auf einen Eltern hantierst die in wirklichkeit ein Kind ist.



  • ~john schrieb:

    SchlechterInformatiker schrieb:

    Also kann ich hergehen, und den Knoten* mit dynamic_cast in einen MeineGruppe* zu verwandeln. Danach kann ich überprüfen ob der erhaltene Zeiger NULL ist oder nicht. Wenn der erhaltene Zeiger nicht NULL ist, dann ist eine "MeineGruppe"-Instanz hinter dem Zeiger.

    Wenn dynamic_cast fehlschlägt, wird eine Exception geworfen.

    Seit wann?



  • padreigh schrieb:

    @~john das mit der Exception stimmt so nicht - zumindest nicht, wenn er wie er dauernd sagt mit pointern arbeitet, der ist dann einfach 0 (s.Api) nur bei Referenzen wird eine geworfen.

    Tschuldigung, da hat mich mein Gedächtnis in die Irre geführt.



  • Ach herrje... ich weiß ich kann nicht besonders gut rüberbringen was ich meine, sorry!

    @manni66: Ich weiß schon dass es mit dynamic_cast funktioniert. Ich will nur wissen ob es nicht auch ohne dynamic_cast ginge.

    @padreigh: Die Klassen haben durchaus weitere Funktionalitäten. Das hab ich aber nicht ins Beispiel mit rein genommen damit es nicht zu sehr aufgebläht wird.

    @~john: Ja das soll tatsächlich dynamic dispatch werden, sonst würd ich das ja auch nicht machen. Ich kenne mich mit Objektorientierung schon gut aus, nur Mehrfachvererbung ist mir nicht besonders geläufig.

    Lassen wir doch mal die Gründe weg wieso ich sowas mache und konzentrieren uns auf die Kernfrage. Ich formuliere das mal neu:

    Ich habe drei Klassen A, B und C. Klasse C erbt von A und von B. Ich nehme eine Liste, die Zeiger auf A-Instanzen speichert, ich speichere in dieser Liste in wirklichkeit aber Zeiger auf C-Instanzen.

    Später hole ich mir ein Element aus der Liste, bekomme also einen Zeiger auf eine A-Instanz, weiß aber dass es sich tatsächlich um einen Zeiger auf eine C-Instanz handelt und möchte deshalb auf ein Attribut zugreifen, welches C von B geerbt hat.

    Geht das ohne dynamic_cast oder geht das nicht?

    MfG



  • SchlechterInformatiker schrieb:

    @~john: Ja das soll tatsächlich dynamic dispatch werden, sonst würd ich das ja auch nicht machen. Ich kenne mich mit Objektorientierung schon gut aus, nur Mehrfachvererbung ist mir nicht besonders geläufig.

    Also bei Dir gibt es einen deftigen Fehler im Beispiel.
    Gruppe:getChild liefert Objekte vom Typ Knoten zurück, diese enthalten aber keinerlei Information, somit kannst Du auch nicht in Besucher::apply(MeineGruppe& myGroup) via Zeiger vom Knoten* Dir das Feld art ausgeben lassen, das ist vollkommen unbekannt!

    Es bietet sich in so einem Fall nur eine abstrakte Basisklasse zu verwenden, vor der alle anderen Klassen abgeleitet werden müssen, welche die Information für den Objekttyp verwaltet.

    Beispiel

    class Meta {
        size_t id_;
    public:
        explicit Meta (size_t id) : id_(id) {}
        virtual ~Meta () = 0;
        virtual size_t typeid () const;
    };
    Meta::~Meta() {}
    size_t Meta::typeid() const {
        return this->id_;
    }
    

    SchlechterInformatiker schrieb:

    Lassen wir doch mal die Gründe weg wieso ich sowas mache und konzentrieren uns auf die Kernfrage.

    Nein, das ist sinnfrei, denn was Du hier versucht nachzuprogrammieren gibt es bereits in C++. Es gibt den typeid Operator, der ein type_info Objekt zurückliefert. Zum Verständnis der Mehrfachverbung sind Deine Fragen ok, aber so sollte man es trotzdem nicht entwerfen.

    SchlechterInformatiker schrieb:

    Geht das ohne dynamic_cast oder geht das nicht?

    Downcast gehen direkt, für Upcasts muß dynamic_cast verwendet werden. Man könnte natürlich in das Framework eine Superklasse einbauen, die ein Interface für dynamisches Dispatching anbietet.

    Um das obige Beispiel zu erweitern:

    class Meta {
        size_t id_;
    public:
        explicit Meta (size_t id) : id_(id) {}
        virtual ~Meta () = 0;
        virtual size_t typeid () const;
        virtual void dynamic_dispatch (std::string& methodeName, boost::share_ptr<Meta> returnValue, std::vector<boost::shared_ptr<Meta> > arguments) = 0;
    };
    Meta::~Meta() {}
    size_t Meta::typeid() const {
        return this->id_;
    }
    

    Dann könnte man via Basiszeiger jede Methode in der Klasse aufrufen, allerdings muß man dann in jeder Klasse dynamic_dispatch überladen und alle Klassenmethoden in dieser Methode einbauen und bekannt machen. Wenn man eine unbekannte Methode aufruft gibt es dann eine Exception. Ob man das unbedingt so machen will, da habe ich meine Zweifel. Man könnte sich natürlich einen Präprozessor schreiben, der diese dynamic_dispatch Methode für jede Klasse automatisch generiert.



  • Hmmm ok...
    Also die Basisklassen sind mir vorgegeben, die kann ich leider nicht verändern und abstrakt machen und denen ein Feld verpassen, welches mir die Informationen liefert die ich brauche.

    Ich dachte halt dass das mit Mehrfachvererbung lösbar wäre und wollte mich bei dieser Gelegenheit da mal einarbeiten, drum hab ich das mal versucht und nachgefragt.

    Danke für die Antworten.


Anmelden zum Antworten