Aufrufen einer redefinierten Funktion der abgeleiteten Klasse durch eine Funktion der Basisklasse
-
Mir ist gestern folgendes aufgefallen. Mal angenommen, wir haben folgenden Quellcode (bei den Klassendefinitionen handelt es sich um Ausschnitte, die Klassen würde natürlich noch mehr Variablen und Funktionen haben):
class Bild { public: virtual void Drehen (int richtung); /*richtung>0 --> Viertel Drehung nach rechts richtung<0 --> Viertel Drehung nach links richtung=0 --> Halbe Drehung*/ virtual void IrgendeineFunktion () { //Irgendein Quelltext Drehen (0); //Führe eine halbe Drehung aus! //Irgendein Quelltext } }; class Sprite: public Bild { public: virtual void Drehen (int grad); /*Verbesserte Drehen-Funktion, die aufs Grad genau drehen kann*/ };Als erstes gleich mal vorweg: Das virtual ist nötig, damit jemand, der folgendes deklariert:
Bild *spieler=new Sprite;auch die Möglichkeit hat, seine neuen, redefinierten Methoden aufzurufen.
Nun mein Problem: Wenn ich jetzt ein Sprite deklariere und rufe IrgendeineFunktion auf, dann wird bei dieser plötzlich die Funktion Sprite::Drehen, statt Bild::Drehen genommen. Aber das ist ja nicht das, was ich will. Ich meine, gut, in einer abgeleiteten Klasse habe ich eine bereits vorhandene Funktion redefiniert und sie besser gemacht. Aber: Eine Funktion aus der Basisklasse, die so eine redefinierte Funktion aufruft, sollte doch trotzdem noch weiter mit der alten Version arbeiten. Ich meine, was geht es denn IrgendeineFunktion an, was eine abgeleitete Klasse redefiniert? IrgendeineFunktion war doch eigentlich dazu gedacht, Bild::Drehen aufzurufen. Sie kann sich doch nicht plötzlich dynamisch Sprite::Drehen nehmen und diese stattdessen aufrufen. Damit ist doch das korrekte Verhalten der bereits vorhandenen Basisfunktionen gar nicht mehr gewährleistet. Denn in der Basisklasse bedeutet eine 0 bei richtung, daß wir eine halbe Drehung haben. In der abgeleiteten Klasse bedeutet 0, daß wir eine Drehung von 0 Grad (also gar keine Drehung) haben. Also funktioniert das Programm nicht so, wie es soll.
Frage 1: Wieso ist das so? Wie kann es sein, daß eine Basisfunktion eine abgeleitete und redefinierte Funktion aufruft, statt diese zu benutzen, die direkt für sie geschrieben wurde? (Das gleiche Problem gäbe es auch, wenn der Parameter in der redefinierten Methode noch die gleiche Bedeutung hätte. Mal angenommen, ich wollte bei jedem Aufruf der neuen Drehen-Funktion eine Ausgabe (zum Beispiel eine Message-Box) realisieren, warum auch immer. Dann soll doch diese Ausgabe aber nicht kommen, wenn Drehen zufällig intern in IrgendeineFunktion aufgerufen wird, denn das war ja noch die alte Drehen-Funktion.)
Frage 2: Gibt es eine Möglichkeit, das zu umgehen, ohne auf das virtual zu verzichten?
-
NES-Spieler schrieb:
Nun mein Problem: Wenn ich jetzt ein Sprite deklariere und rufe IrgendeineFunktion auf, dann wird bei dieser plötzlich die Funktion Sprite::Drehen, statt Bild::Drehen genommen.
Doch, genau das willst Du (zumindest sagst Du das dem Compiler). Dafür hast Du die Funktion virtual definiert. Wieso sollte der Compiler beim Aufruf von innerhalb oder ausserhalb der Klasse einen Unterschied machen?
Ich meine, was geht es denn IrgendeineFunktion an, was eine abgeleitete Klasse redefiniert?
Genau dafür ist virtual doch da

Frage 2: Gibt es eine Möglichkeit, das zu umgehen, ohne auf das virtual zu verzichten?
Ja, Du kannst eine zweite Drehen-Funktion mit anderem Namen mit Rücksicht auf die andere Semantik definieren. Wenn jemand von Aussen bei einem Bild* Drehen verwendet, woher soll der denn wissen, dass Du in Sprite plötzlich die Bedeutung des Parameters änderst? Ist doch die gleiche Situation. Gleichnamige Funktionen mit gleichen Parametern mit völlig unterschiedlichen Bedeutungen zu versehen, ist IMHO einfach nur ein prekärer Designfehler.
-
Genau dafür ist virtual doch da
Also ich dachte immer, virtual ist für sowas gedacht:
Bild *objekte [3]; objekte [0]=new Sprite; objekte [1]=new Hintergrund; objekte [2]=new Gegenstand; for (int n=0; n<3; n++) objekte [n]->Ausgabe (); //Ausgabe ist eine redefinierte Funktion.Das bedeutet aber nicht, daß eine Funktion, die nur in der Basisklasse definiert ist (IrgendeineFunktion) und dort eine andere Basisklassenfunktion (Bild::Drehen) aufruft, in der abgeleiteten Klasse plötzlich die redefinierte Funktion (Sprite::Drehen) aufrufen soll (obwohl IrgendeineFunktion selbst nirgendwo neu definiert wurde).
Gleichnamige Funktionen mit gleichen Parametern mit völlig unterschiedlichen Bedeutungen zu versehen, ist IMHO einfach nur ein prekärer Designfehler.
O.k., gut, dann war das ein schlechtes Beispiel. Hier also etwas anderes:
class Bild { public: virtual void Laden (std::string dateiname) { //Irgendwelcher Quatsch Drehen (); //Irgendwelcher Quatsch } virtual void Drehen (); }; class Sprite: public Bild { virtual void Drehen () { char auswahl; cout<<"Sie wollen also wirklich das Bild drehen? (j/n) "; cin>>auswahl; if (auswahl=='j') { cout<<"Wirklich? "; cin>>auswahl; if (auswahl=='j') { cout<<"Ganz sicher? "; cin>>auswahl; if (auswahl=='j') { cout<<"Na gut, Sie haben es nicht anders gewollt.\n"; Bild::Drehen (); cout<<"O.k., das Bild ist jetzt gedreht. Sind Sie nun zufrieden?"; } } } } };Wann immer ich die neue Drehen-Funktion des Sprites aufrufe, soll erst diese lästige Abfrage kommen (der Grund sei mal dahingestellt, es ist ja nur ein Beispiel). Aber das bedeutet doch noch lange nicht, daß sich nun auch die Funktionalität von Laden (welche ja in Sprite nichtmal neu definiert wurde) ändern soll. Wenn die intern die Drehen-Funktion aufruft, soll diese Abfrage natürlich nicht kommen, eben schon deshalb, weil Laden danach ausgelegt ist, mit Bild::Drehen zu arbeiten und nicht mit der neuen Sprite::Drehen-Funktion.
Du kannst eine zweite Drehen-Funktion mit anderem Namen mit Rücksicht auf die andere Semantik definieren.
Was ich meinte, war: Wenn ich jemandem eine Klasse gebe und der redefiniert eine meiner Methoden, so sollen meine Funktionen der Basisklasse trotzdem noch zuverlässig mit meinen anderen Funktionen laufen und nicht mit seinen. Nur, wenn er einen neuen Aufruf der doppelten Funktion erschafft (entweder in seiner Klasse oder außerhalb durch ein Objekt seiner Klasse), soll seine genommen werden.
-
NES-Spieler schrieb:
Also ich dachte immer, virtual ist für sowas gedacht:
Und was bitte spricht dagegen, innerhalb der Klasse eine Ausgabe tätigen zu wollen? Oder sollte diese Ausgabe nach Deinem Verständnis nur den Bild-Teil, nicht den des konkreten Objektes ausgeben?
Beispiel:
Eine Basisklasse definiert eine Methode speichern(filename). Hier wird eine Datei geöffnet, Fehlerbehandlung getrieben, Benutzerinteraktion getätigt. Zusätzlich definiert sie eine virtuelle Methode schreiben(ostream&), die in speichern mit dem geöffneten Dateistream aufgerufen wird. Nach Deiner Logik könnte die Methode speichern niemals das gesamte Objekt speichern, sondern immer nur den Bild-Teil.Wenn Du unbedingt auf dem Namen bestehst, mache aus Richtung ein enum und definiere Drehen zweimal, einmal mit Richtung, einmal mit int. Dann kannst Du beide getrennt überschreiben (oder auch nicht).
...........................................................................
Hallo, Forum, ich versuche hier einen Beitrag zu erstellen verdammte Hacke, soll ich den jetzt ausdrucken und dahein wieder eintippen...
-
Ja, o.k., ich merke schon, worauf Du hinauswillst. Es gibt sicherlich Stellen, wo man das so anwenden möchte. Trotzdem finde ich, stanardmäßig sollten einmal definierte Funktionen, welche andere Funktionen aufrufen, auch nur diese direkt aufrufen und sich in der Vererbungshierarchie nicht die unterste redefinierte Variante raussuchen.
Bei mir gibt es zum Beispiel eine Funktion Fill (COLORREF color). Wenn nun jemand denkt, er müßte Fill redefinieren, so daß das Bild nun nicht mehr mit einer Farbe, sondern in Wellenlinien mit der angegebenen und der invertierten Farbe befüllt wird, so kann er das gerne machen. Aber: Solange er meine CreateImage-Funktion nicht auch redefiniert, sondern die aus der Basisklasse nimmt, soll diese doch beim Erstellen eines leeren Bildes meine alte Funktion Fill aufrufen und nicht die neue, so daß ein leeres Bild weiterhin komplett schwarz ist und nicht aus schwarzen und weißen Wellenlinien besteht, auch wenn die neue Fill-Funktion das so macht.
-
Versuchs mal mit
Bild::Drehen();
in der Load Funktion. (Bin mir nicht sicher aber imho sollte sich das so erzwingen lassen).
-
//edit: Knuddl war fixer, ich lass' es wegen dem Code etc. trotzdem mal stehen

Was IMHO gegen das Prinzip der Vererbung verstossen würde, da man sich damit ja zum Beispiel gerade eben den Aufwand, die CreateImage neu zu definieren, sparen kann.
Und wenn du dann wirklich in der Basismethode explizit die Basisfunktion aufrufen willst, dann gibts auch dafür 'ne Möglichkeit:
//WJ Codings proudly presents: Eine sehr komplizierte Möglichkeit, a auszugeben. #include <iostream> class a { public: void out() { std::cout << "a"; } void foo() { a::out(); } }; class b : public a { public: void out() { std::cout << "b"; } }; int _tmain(int argc, _TCHAR* argv[]) { a *pa = new b(); pa->foo(); std::cin.get(); return 0; }