doppelte Vererbung



  • Hallo,

    ich hab im Moment ein Problem bei der Verwendung von zwei Interfaces:

    class IPlayer
    {
    public:
    	virtual bool IsValid() = 0;
    };
    
    class ILocalPlayer : public IPlayer
    {
    public:
    	virtual bool CanShoot() = 0;
    };
    
    class SourcePlayer : public IPlayer
    {
    public:
    	SourcePlayer(int index)
    		: index(index)
    	{
    
    	}
    
    	virtual bool IsValid()
    	{
    		return true;
    	}
    
    private:
    	int index;
    };
    
    class SourceLocalPlayer : public SourcePlayer, public ILocalPlayer
    {
    public:
    	SourceLocalPlayer(int index)
    		: SourcePlayer(index)
    	{
    
    	}
    
    	virtual bool CanShoot()
    	{
    		return false;
    	}
    };
    
    int main(int argc, char **argv)
    {
    	SourceLocalPlayer *local = new SourceLocalPlayer(1);
    
    	delete local;
    }
    

    Es gibt Spieler, die das Interface IPlayer implementieren. Einer der Spieler ist der lokale Spieler, der dann noch das ILocalPlayer Interface implementieren soll. Ich dachte eigentlich, dass das so funktionieren würde, tut es aber offenbar nicht, denn der Compiler gibt aus, dass SourceLocalPlayer abstrakt ist mit dem Hinweis auf IsValid. Ich dachte, das würde dann SourcePlayer schon implementieren?!
    Wie kann ich das Problem am elegantesten lösen?

    greetz KN4CK3R



  • Ich sehe zwei Probleme in deinem Code.

    1. Deadly Diamond Problem (Google hilft da)
    2. Mach deine Destruktoren virtual



  • Da hast du den "Diamond of Death", d.h. die Basisklasse IPlayer wird doppelt vererbt. Entsprechend werden auch ihre Funktionen zwei Mal vererbt und eine davon ist abstrakt. Ob und wie man diese überschreiben kann, weiß ich nicht genau, aber generell ist hier eine Designänderung angebracht. Leider ist mir nicht ganz klar, was du hiermit erreichen willst. Aber vielleicht kannst du ja die verschiedenen Eigenschaften einzeln als Variablen in IPlayer speichern. Also mit zwei abstrakten Basisklassen: LocalProperties und SourceProperties o.ä.



  • Gehen würde es so:

    class ILocalPlayer : virtual public IPlayer
    {
        // ...
    };
    
    class SourcePlayer : virtual public IPlayer
    {
        // ...
    };
    


  • sehr guter Tipp, Wikipedia FTW 🙂

    der Vollständigkeit halber:

    class Player
    {
    public:
    	virtual bool IsValid() = 0;
    };
    
    class LocalPlayer : virtual public Player
    {
    public:
    	virtual bool CanShoot() = 0;
    };
    
    class SourcePlayer : virtual public Player
    {
    public:
    	SourcePlayer(int index)
    		: index(index)
    	{
    
    	}
    
    	virtual bool IsValid()
    	{
    		return true;
    	}
    
    private:
    	int index;
    };
    
    class SourceLocalPlayer : public SourcePlayer, public LocalPlayer
    {
    public:
    	SourceLocalPlayer(int index)
    		: SourcePlayer(index)
    	{
    
    	}
    
    	virtual bool CanShoot()
    	{
    		return false;
    	}
    };
    

    Danke!

    greetz KN4CK3R



  • KN4CK3R schrieb:

    ...

    Hast du dir das Diamond-Problem wenigstens zu Herzen genommen?



  • jap, ganz so lösen werd ichs nicht, sondern das Design überdenken. Das "Danke!" war mehr auf das virtual public Base bezogen, das kannte ich noch nicht.

    greetz KN4CK3R



  • Gugelmoser schrieb:

    KN4CK3R schrieb:

    ...

    Hast du dir das Diamond-Problem wenigstens zu Herzen genommen?

    Der Weg über Interfaces ist doch okay. Das ist gerade das Mittel der Wahl um Mehrfachvererbung ohne die Nachteile zu ermöglichen. Siehe zum Beispiel auch Java.



  • eine mir zusagende Lösung hab ich immer noch nicht gefunden...
    Ich erläutere mal die aktuelle Problemstellung.
    Ich hab im Spiel mehrere Spieler, einer davon ist der lokale Spieler. Bei diesem lokalen Spieler habe ich mehr Methoden als bei den anderen (readonly) Spielern (z.B. bewegen o.ä.), ist also quasi eine Erweiterung der normalen Spieler.

    Im Code habe ich nun folgendes Konstrukt:

    ILocalPlayer *localPlayer = GetLocalPlayer();
    
    for (auto it = GetPlayerIterator(); it; ++it)
    {
    	IPlayer *player = *it;
    	if (!localPlayer->Equals(player) && player->IsValid())
    	{
    		//mach was mit player
    		//Abstand berechnen etc Vector delta = localPlayer->GetOrigin() - player->GetOrigin();
    		//hier benötige ich dann das IPlayer Interface von ILocalPlayer
    	}
    
    	delete player;
    }
    
    localPlayer->moveTo(...);
    
    delete localPlayer;
    

    Probleme bereitet jetzt die Equals Methode. Dadurch, dass jetzt die Basisklasse virtuell ist, verweigert der Compiler den cast. Bisher sah mein Equals so aus:

    class IPlayer
    {
    public:
        virtual bool IsValid() = 0;
        virtual bool Equals(const IPlayer *other) = 0;
    };
    
    class SourcePlayer : virtual public IPlayer
    {
    public:
        SourcePlayer(int index)
            : index(index)
        {
    
        }
    
        virtual bool IsValid()
        {
            return true;
        }
    
        virtual bool Equals(const Player *other)
        {
            const SourcePlayer *p = static_cast<const SourcePlayer*>(other);
            return index == p->index;
        }
    
    private:
        int index;
    };
    

    Jetzt beschwert sich der Compiler beim Cast und ein reinterpret_cast liefert natürlich nur noch Datenmüll.
    Wie könnt ich das Problem lösen? Ob Code oder Designvorschlag ist mir relativ egal.

    greetz KN4CK3R



  • Du willst einen SourcePlayer mit einem SourcePlayer vergleichen. Dann solltest Du das auch ausdrücken:

    bool Equals(const SourcePlayer *other) //nicht polymorph, da ohnehin invariant
        {
            return index == other->index;
        }
    


  • in dem Fall kann ich aber nicht mehr vom allgemeinen IPlayer, den ich in meiner Schleife habe, Equals aufrufen. Irgendwo muss ich ja hochcasten damit ich Zugriff auf index habe. Das hochcasten ist an der Stelle in Ordnung, da garantiert ist, dass immer nur eine Art von Player vorkommt.
    Als ich das gestern in VS getestet habe, stand nach dem cast nur Blödsinn in index, das ist beim gcc offenbar anders, dafür bekomme ich eine free Fehlermeldung 😕 (auch ohne Equals)

    http://ideone.com/q09i8

    greetz KN4CK3R



  • Du hast immer noch keinen virtuellen Dtor. Abgesehen davon finde ich deine Klassennamen schrecklich. Was ist denn bitte ein "LocalPlayer" oder ein "SourceLocalPlayer" und warum zur Hölle ein DoD.



  • Das ist natürlich nur ein Minimalbeispiel hier fürs Forum *Kopfschüttel*
    Die Klassen heißen so weil es dabei um die SourceEngine geht. In meinem Projekt heißen die Klassen auch anders.
    Wie man da den DoD vermeiden kann, war gerade meine Frage, falls du die bisherigen Posts nicht lesen wolltest...

    greetz KN4CK3R



  • KN4CK3R schrieb:

    Wie man da den DoD vermeiden kann, war gerade meine Frage...

    aber es wäre besser, einen neuen Blickwinkel zu finden 😉

    z.B:

    struct IShooter {
    
       virtual bool CanShoot () const = 0;
    };
    
    enum PlayerChar {
       LocalPlayerChar,
       SourcePlayerChar
    };
    
    struct IPlayer {
       virtual bool isValid () const =0;
       virtual IShooter * getShooter () =0;   
    };
    
    ...
    


  • ich hab mir sowas ähnliches überlegt:

    class IPlayer
    {
    public:
    	virtual bool IsValid() = 0;
    	virtual bool Equals(const IPlayer &player) = 0;
    };
    
    class ILocalPlayer : public IPlayer
    {
    public:
    	virtual bool CanShoot() = 0;
    };
    
    class SourceLocalPlayer: public ILocalPlayer
    {
    public:
    	SourceLocalPlayer(SourcePlayer &player)
    		: player(player)
    	{
    
    	}
    
    	virtual bool IsValid()
    	{
    		return player.IsValid();
    	}
    
    	virtual bool CanShoot()
    	{
    		return false;
    	}
    
    	virtual bool Equals(const IPlayer &player)
    	{
    		return this->player.Equals(player);
    	}
    
    private:
    	SourcePlayer &player;
    };
    

    hier bekommt der LocalPlayer dann sein eigentliches Player Objekt im Konstruktor mit und ruft dann jeweils dessen Implementierung des Interfaces auf. Dadran stört mich aber etwas, dass die SourceLocalPlayer "is a" SourcePlayer Verbindung verloren geht und ich quasi alle Funktionsaufrufe weiterleite (solange sie gleich sind).

    greetz KN4CK3R



  • class ILocalPlayer : public IPlayer
    {
    public:
    virtual bool CanShoot() = 0;
    };

    class SourceLocalPlayer: public ILocalPlayer
    {
    public:
    SourceLocalPlayer(SourcePlayer &player)
    : player(player)

    eigentlich sieht das nicht schön aus.. können Sie erzählen, welche Unterschiede es zwischen Player-Klassen gibt ? dann wird es leichter eine richtige Lösung zu finden.. 🙂



  • wie gesagt gibt es die normalen Spieler, deren Hauptunterschied zum lokalen Spieler ihr Schreibschutz ist. Auf andere Spieler kann nur lesend zugegriffen werden. (GetName, GetHealth, GetOrigin, ...) Der lokale Spieler bietet die gleichen Methoden an (er ist ja auch einer der Spieler), bietet aber noch zusätzlich Methoden an, mit denen er verändert werden kann (SetAngles, Shoot, MoveForward, ...)

    Also dachte ich eben an die von mir erstellen Interfaces

    class IPlayer
    {
    public:
    	GetName = 0;
    	GetHealth = 0;
    	GetOrigin = 0;
    	...
    };
    
    class ILocalPlayer : public IPlayer //Vererbung weil ILocalPlayer ist ein IPlayer
    {
    public:
    	SetAngles = 0;
    	Shoot = 0;
    	MoveForward = 0;
    	...
    };
    
    class ConcretePlayer : public IPlayer //ist ein IPlayer
    {
    public:
    	...IPlayer Methoden...
    };
    
    class ConcreteLocalPlayer : public ConcretePlayer, public ILocalPlayer //ist ein ConcretePlayer und ist ein ILocalPlayer => DoD
    {
    public:
    	...ILocalPlayer Methoden...
    	//sollte von ConcretePlayer erben, damit hier nicht noch alle IPlayer Methoden implementiert werden müssen
    };
    

    greetz KN4CK3R



  • KN4CK3R schrieb:

    Also dachte ich eben an die von mir erstellen Interfaces

    dieser Vorgang nimmt viel Mühe umsonst..

    KN4CK3R schrieb:

    wie gesagt, gibt es die normalen Spieler, deren Hauptunterschied zum lokalen Spieler ihr Schreibschutz ist. Auf andere Spieler kann nur lesend zugegriffen werden.

    es darf mithilfe von zwei Funktionen gelöst werden : getPlayerInfo () und getPlayerInfoConst.. und keine unnötige Vererbung..



  • du meinst ILocalPlayer sollte gar nicht von IPlayer erben, sondern nur mit einer Methode einen IPlayer zurückgeben?

    greetz KN4CK3R



  • KN4CK3R schrieb:

    du meinst ILocalPlayer sollte gar nicht von IPlayer erben, sondern nur mit einer Methode einen IPlayer zurückgeben?

    es kann sein.. um diese Frage zu beantwotrten, ist es mehr Information nötig, was diese Klasse darstellen.


Anmelden zum Antworten