Frage zur Vererbung
-
#include <iostream> class Eins { public: void test() { std::cout << "Methode Eins::test" << std::endl; } }; class Zwei : public Eins { public: void test(int i) { std::cout << "Methode Zwei::test mit Arg" << std::endl; } }; class Drei : public Zwei { }; int main() { Drei d; d.test(); /// error C2660: 'Zwei::test': Funktion akzeptiert keine 0 Argumente }
Offensichtlich verfügt die Klasse Drei nicht über die Methode test().
Ist das Verhalten so korrekt? Das bedeutet ja, dass ich, wenn ich über mehr als zwei Generationen vererbe, eine bestehende Menge an Methoden nicht erweitern kann indem ich eine (Klassenbezüglich) spezialisierte Methode mit gleichem Namen und unterschiedlichen Parametern hinzufüge.
Ich dachte immer, dass die Signatur einer Methode per Klassenname+Methodenname+Parametertypen bestimmt wird. Demzufolge hätte solches Verhalten wie oben gar keinen Sinn. Oder doch? Und wenn ja, welchen?
-
Schlag in deinen C++-Unterlagen mal nach dem Schlüsselwort virtual nach, da findest du (wahrscheinlich) das was du suchst.
-
Helper schrieb:
Schlag in deinen C++-Unterlagen mal nach dem Schlüsselwort virtual nach, da findest du (wahrscheinlich) das was du suchst.
IMHO sorgt virtual nur dafür, dass es eine objektspezifische Reaktion auf ein und diesselbe Anfrage entlang einer Vererbungshierarchie gibt.
Wenn ich also einen Zeiger auf eine Instanz einer Klasse auf einen Typ einer Basisklasse "downcaste" und darauf dann einen Methodenaufruf starte der eine virtual deklarierte Methode aufruft, wird die Methode der eigentlichen Klasse aufgerufen anstatt die Methode der Basisklasse. Das ist vertikale Polymorphie und hat meiner Meinung nach mit meinem Problem nichts zu tun.#include <iostream> class Eins { public: virtual void test() { std::cout << "Methode Eins::test" << std::endl; } }; class Zwei : public Eins { public: void test(int i) { std::cout << "Methode Zwei::test mit Arg" << std::endl; } }; class Drei : public Zwei { void test() { std::cout << "Drei::test" << std::endl; } }; int main() { Drei *d = new Drei; Eins *e = dynamic_cast<Eins*>(d); e->test(); /// Ausgabe: Drei::test }
-
Zwei::test(int) verdeckt Eins::test()
du musst in Zweiusing Eins::test;
schreiben damit auch Eins::test() sichtbar ist
-
Da Drei ja von Zwei ableitet, und du an Drei nichts veränderst ist es im Prinzip ja die Klasse Zwei. Und in Zwei benötigt die Methode test nunmal einen Integer - den du nicht übergibst.
-
Vielleicht findest du hier die Antwort/Erkärung auf deine Frage:
http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.6
-
Danke für die Antworten!
Mir war das Problem bis dato unbekannt.
Welchen Sinn hat dieses Verhalten bzw. was wird damit bezweckt? (in Java ist das ja nicht so)Übrigens habe ich nebenbei noch etwas gemerkt, was mich etwas stutzig macht:
Im zweiten Code-Beispiel rufe ich nach einem Downcast sozusagen "indirekt" über Eins* die Methode Drei::test() von ausserhalb der Klasse auf. Diese ist allerdings private und sollte sich garnicht aufrufen lassen! Wenn ich es "direkt" versuche, kann ich es nicht, über den Umweg allerdings schon.
Diese Tatsache finde ich auch etwas merkwürdig...
-
Warum ist Drei::test() private
-
Drakos schrieb:
Diese ist allerdings private und sollte sich garnicht aufrufen lassen! Wenn ich es "direkt" versuche, kann ich es nicht, über den Umweg allerdings schon.
Diese Tatsache finde ich auch etwas merkwürdig...in Eins ist test aber nicht private. und da du einen zeiger auf Eins verwendest...
oderDrei d; d.Eins::test(); d.test();
-
Drakos schrieb:
Welchen Sinn hat dieses Verhalten bzw. was wird damit bezweckt? (in Java ist das ja nicht so)
Kranke und Perverse 'Lookup' Regeln, nach denen der Compiler den richtigen Scope sucht.
Wenn du
test()
schreibst, dann sucht er alle moeglichen Sichtbarkeitsbereiche durch und hoert auf, sobald er ein test() findet - dabei achtet er nicht auf die Parameter. Diese werden erst im zweiten Schritt betrachtet.dh er findet Zwei::test und ist gluecklich. Jetzt versucht er die Argumente zu 'matchen' - das schlaegt aber fehl, denn Zwei::test nimmt einen int als Parameter, du gibst ihm aber keinen.
Die Lookup-Regeln machen C++ extrem komplex. Zum Glueck muessen sich damit fast nur die Compilerhersteller rumschlagen und wir Programmierer kommen damit nur selten in Beruehrung
-
Shade Of Mine schrieb:
...
sucht er alle moeglichen Sichtbarkeitsbereiche durch und hoert auf, sobald er ein test() findet - dabei achtet er nicht auf die Parameter. Diese werden erst im zweiten Schritt betrachtet.dh er findet Zwei::test und ist gluecklich. Jetzt versucht er die Argumente zu 'matchen' - das schlaegt aber fehl, denn Zwei::test nimmt einen int als Parameter, du gibst ihm aber keinen.
...Und ich dachte immer, es werden erst die Signaturen für die Methoden erstellt und die hätten dann nach der Zusammenstellung Namensraum+Klasse+Methodenname+Parametertypen einen eindeutigen Identifizierer! Naja, wenn man weiss wie man des ganze umgehen kann...schön ist es nicht gerade!
-
Nein, es ist extra so gedacht. Das ganze virtal zeugs is nur dazu da um in abgeleiteten Klassen Methoden überladen zu können und per pointer vom Typ Basisklasse beliebige Anzusprechen.
Intern gibts für die Klasse eine pointer-tabelle (vtable) aus der dann die richtige Methode angesprochen wird. Dadurch wird sie identifizierbar.