Interfaces wie in Java?



  • @mathias: dein "tolles" Bsp. dürfte nen Fehler beim Kompilieren bringen wegen Mehrdeutigkeit 🙄 🤡



  • Sollte meine Beipsiele vorher austesten. *grummel* Sorry Leuts, schlechtes Beispiel.
    (Hatte das Beispiel warum man das virtuelle Ableiten braucht im Kopf und hab das auf virtuelle-Funktionen übertragen und nicht bedacht, dass der Compiler das erkennen kann.)



  • Meld dich wieder, wenn du auch einen Grund hast. Ist das gleiche wie, als mich ein Kunde gefragt hat, ob man auch die Buttons in der Toolbar ändern kann, nachdem ich ihn gefragt habe, was denn an dem System so schlecht wäre. Da hab ich mich drüber gefreut, das er nichts besseres zum meckern gefunden hat. 😃



  • Auch wenn in den beiden Basisklassen das nicht virtuell wäre gibt es ne Mehrdeutigkeit 🙂 Zum Thema Mehrfachvererbung verweise ich auch gerne auf "Effektiv C++" gibt's ein feines Kapitel zu



  • Meld!

    Basisinitialisierer (Aufruf des entprechenden Konstruktors) für virtuelle Basisklassen, die im Konstruktor einer darüberliegenden Klasse stehen, werden ignoriert.
    🙂



  • @Pellaeon
    Das Standard-Beispiel zum Virtuellen Ableiten verwendet an der Stelle Membervariablen und da gibt es werder Mehrdeutigkeit noch Fehlermeldungen anderer Art.



  • ich meinte wenn Foo überhauptz nicht virtuell ist und in B sowie C deklariert wäre



  • @Pellaeon
    Wäre ansonsten auch merkwürdig. 🙂

    Ansonsten hab ich nochmal etwas nach gelesen. Mein Beispiel ist durchaus zutreffend, wenn auch mit ungünstigen Bezeichnern und kein Laufzeitproblem.
    Das Problem der Mehrfachvererbung ist hauptsächlich ein konzeptioneles (z.B. Fahrzeug mit der Funktion MaxGeschwindigkeit und davon abgeleitet Schiff und Automobil und von diesen beiden Amphibienfahrzeug.
    Selbst durch das Meckern des Compilers lässt sich dieses Problem nicht nur das Überschreiben von MaxGeschwindigkeit in Amphibienfahrzeug lösen, da Amphibienfahrzeug zwei unterschiedliche Maximalgeschwindigkeiten (eine zu Land und eine im Wasser) besitzt.
    Klingt sehr theoretisch ist aber bei gewachsenen Systemen durchaus ein großes Problem, da man da nicht einfach mal die Klassenstruktur umstellen kann.)



  • Aber das Amphibienfahrzeug weiß doch, ob es auf dem Wasser ist 😉

    #include <iostream>
    using namespace std;
    
    class Fahrzeug
    {
    public:
    	virtual int geschwindigkeit() const = 0;
    };
    
    class Wasserfahrzeug : public Fahrzeug
    {
    public:
    	virtual int geschwindigkeit() const { return 10; }
    };
    
    class Landfahrzeug : public Fahrzeug
    {
    public:
    	virtual int geschwindigkeit() const { return 50; }
    };
    
    class Amphibienfahrzeug : public Wasserfahrzeug, public Landfahrzeug
    {
    	bool m_istAufDemWasser;
    public:
    	Amphibienfahrzeug(bool istAufDemWasser = false)
    		: m_istAufDemWasser(istAufDemWasser) {}
    
    	bool istAufDemWasser() const { return m_istAufDemWasser; }
    	void istAufDemWasser(bool istAufDemWasser) { m_istAufDemWasser = istAufDemWasser; }
    
    	virtual int geschwindigkeit() const
    	{
    		return istAufDemWasser() ? Wasserfahrzeug::geschwindigkeit() : Landfahrzeug::geschwindigkeit();
    	}
    };
    
    int main ()
    {
        Amphibienfahrzeug test;
        cout << test.geschwindigkeit() << endl;
        test.istAufDemWasser(true);
        cout << test.geschwindigkeit() << endl;
        cin.get();
    }
    


  • Es gibt aber noch Boost Interface. Die unterscheiden sich von abstrakt virtuellen Klassen: http://www.cdiggins.com/bil.html#abc

    In Java gibt es keine explizite virtualisierung. In C++ ist aber virtual nichts, was man leichtfertig benutzen sollte!



  • @schorsch code
    Durchaus ein Lösungsansatz, nur hast du dir dadurch die Schnittstelle für Fahrzeuge ausgehebelt (Auslesen der technischen Daten des Amphibienfahrzeugs wird zum Sonderfall, weil du um den Hebel umlegen zu können casten musst).
    🙂



  • @Mathias: Super! Danke! Jetzt komme ich den Problemen so langsam auch auf die Schliche. Obwohl eine Methode aller parallelen Basisklassen aus einer gemeinsamen Basisklasse ist, sprich die selbe sein sollte, ist sie dennoch mehrdeutig:

    #include <iostream>
    using namespace std;
    
    class Fahrzeug
    {
    public:
        virtual int geschwindigkeit() const = 0;
        void zeigeGeschwindigkeit() const // ist in allen Subklassen gleich!
        {
        	cout << geschwindigkeit() << endl;
        }
    };
    
    class Wasserfahrzeug : public Fahrzeug
    {
    public:
        virtual int geschwindigkeit() const { return 10; }
    };
    
    class Landfahrzeug : public Fahrzeug
    {
    public:
        virtual int geschwindigkeit() const { return 50; }
    };
    
    class Amphibienfahrzeug : public Wasserfahrzeug, public Landfahrzeug
    {
        bool m_istAufDemWasser;
    public:
        Amphibienfahrzeug(bool istAufDemWasser = false)
            : m_istAufDemWasser(istAufDemWasser) {}
    
        bool istAufDemWasser() const { return m_istAufDemWasser; }
        void istAufDemWasser(bool istAufDemWasser) { m_istAufDemWasser = istAufDemWasser; }
    
        virtual int geschwindigkeit() const
        {
        	if(istAufDemWasser())
    			return Wasserfahrzeug::geschwindigkeit();
    		return Landfahrzeug::geschwindigkeit();
        }
    };
    
    void zeigeGeschwindigkeit(const Fahrzeug &f)
    {
    	f.zeigeGeschwindigkeit();
    }
    
    template<typename T>
    void bitteZeigeGeschwindigkeit(const T &fahrzeug)
    {
    	cout << fahrzeug.geschwindigkeit() << endl;
    }
    
    int main ()
    {
        Amphibienfahrzeug test;
        //Fahrzeug *test2 = new Amphibienfahrzeug(); //geht nicht, weil Fahrzeug in Amphibienfahrzeug mehrdeutig ist
        //test.zeigeGeschwindigkeit(); //geht nicht wegen Mehrdeutigkeit
        //zeigeGeschwindigkeit(test); //geht nicht, weil Fahrzeug in Amphibienfahrzeug mehrdeutig ist
        bitteZeigeGeschwindigkeit(test);
        test.istAufDemWasser(true);
        //test.zeigeGeschwindigkeit(); //geht nicht wegen Mehrdeutigkeit
        bitteZeigeGeschwindigkeit(test);
        cin.get();
    }
    

    Wenn man virtual ableitet geht es aber. Scheut man sich vor dynamic_cast, um spezielle Methoden zu benutzen (hat nichts mit Mehrfachvererbung zu tun), kann man auch die Basisklasse erweitern und bei nicht unterstützten Features Ausnahmen werfen, was natürlich nicht immer Sinn macht. Bei den Fahrzeugen könnte das so aussehen:

    #include <iostream>
    using namespace std;
    
    class Fahrzeug
    {
    public:
    	virtual ~Fahrzeug(){}
    
    	virtual bool istAufDemWasser() const = 0;
        virtual void istAufDemWasser(bool istAufDemWasser) = 0;
    
        virtual int geschwindigkeit() const = 0;
        void zeigeGeschwindigkeit() const { cout << geschwindigkeit() << endl; }
    };
    
    class Wasserfahrzeug : virtual public Fahrzeug
    {
    public:
    	virtual bool istAufDemWasser() const { return true; };
        virtual void istAufDemWasser(bool istAufDemWasser)
        {
        	if(!istAufDemWasser)
    			throw "Wasserfahrzeug ist auf Grund gelaufen!";
        };
    
        virtual int geschwindigkeit() const { return 10; }
    };
    
    class Landfahrzeug : virtual public Fahrzeug
    {
    public:
    	virtual bool istAufDemWasser() const { return false; };
        virtual void istAufDemWasser(bool istAufDemWasser)
        {
        	if(istAufDemWasser)
    			throw "Landfahrzeug ist abgesoffen!";
        };
    
        virtual int geschwindigkeit() const { return 50; }
    };
    
    class Amphibienfahrzeug : public Wasserfahrzeug, public Landfahrzeug
    {
        bool m_istAufDemWasser;
    public:
        Amphibienfahrzeug(bool istAufDemWasser = false)
            : m_istAufDemWasser(istAufDemWasser) {}
    
        bool istAufDemWasser() const { return m_istAufDemWasser; }
        void istAufDemWasser(bool istAufDemWasser) { m_istAufDemWasser = istAufDemWasser; }
    
        virtual int geschwindigkeit() const
        {
        	if(istAufDemWasser())
    			return Wasserfahrzeug::geschwindigkeit();
    		return Landfahrzeug::geschwindigkeit();
        }
    };
    
    void zeigeGeschwindigkeit(const Fahrzeug &f) { f.zeigeGeschwindigkeit(); }
    
    int main ()
    {
        Amphibienfahrzeug test;
        test.zeigeGeschwindigkeit();
        test.istAufDemWasser(true);
        zeigeGeschwindigkeit(test);
    
        Fahrzeug *test2 = new Amphibienfahrzeug();
        zeigeGeschwindigkeit(*test2);
        test2->istAufDemWasser(true);
        test2->zeigeGeschwindigkeit();
        delete test2;
    
        cin.get();
    }
    


  • Das virtuelle Ableiten ist an der Stelle nicht wirklich eine Hilfe. Implementiert man die maximale Geschwindigkeit als Variable (durchaus sinnvoll) wäre das virutelle Ableiten falsch (man hätte wieder nur eine maximale Geschwindigkeit im Attributsatz und nicht derer zwei).

    Das mit dem Typecast und dem Erweitern der Basisklasse ist so eine Sache. Man sollte nie hingehen und Wissen (Variablen/Funktionen) die zu abgeleiteten Klassen gehören in die Basisklasse stecken. Ansonsten besitzen plötzlich andere abgeleitete Klassen Funktionen oder Variablen, die keinen Sinn ergeben.

    Allgemeine Lösung:

    class Fahrzeug
    {
    public:
        virtual int GetMaxGeschwindigkeit (EMedium eMedium = EMedium::eNix) const;
    };
    

    Nicht Mehrdeutig, kann immer einen Wert zurückliefern (gegebenenfalls 0 wenn es sich in dem Medium nicht bewegen kann) und auch auf Flugzeuge, Raumschiffe und UBoote anwendbar. 🙂

    Typumwandlung um Sonderfälle zu behandeln, die die Schnittstelle der Basisklasse nicht abdeckt, sollte man soweit wie möglich vermeiden. (Würde bedeuten es überall und in alle Ewigkeit berücksichtigen zu müssen. Auf die Lebenszeit eines Programms gesehen fatal, weil sich dadurch immer mehr Sonderfälle ansammeln bis sie einem über den Kopf wachsen.)



  • Hallo!
    Nur mal noch eine kleine Zwischenfrage
    zu meinem ursprünglichen Thema:
    Warum wird eigentlich ein virtueller Destruktor
    der Basisklasse auch dann aufgerufen, wenn er
    doch in der abgeleiteten Klasse redefiniert wurde?
    Ich kann dort zwar problemlos einen leeren
    Standarddestruktor implementieren, aber das sollte
    einem ja auch einer sagen: ansonsten wird anscheinend
    gar kein Destruktor ausgeführt, auch nicht der der
    abgeleiteten Klasse!

    Alex



  • Destruktor wird immer ausgeführt, bei Vererbung von unten nach oben in der Hierarchie.
    Virtual brauchst du, wenn du Polymorphie ausnutzt.
    Wenn du z.B. einen Zeiger vomn Typ der Basisklasse hast, dieses aber auf eine abgeleitete Klasse zeigt, wird NUR der Konstruktor der Basisklasse beim Zerstören aufgerufen, wenn du kein virtual vorm Destruktor hast



  • OK, muss ich mir merken.
    (hat ein weilchen gedauert, bis ich das rausgefunden hatte,
    weil er im Debugmodus einfach nicht in in die delete-Operation
    (sprich den Destruktor) reinspringen wollte...)
    Danke,
    Alex


Anmelden zum Antworten