Betrachten Sie den Code:
#include <stdio.h>
class Base {
public:
virtual void gogo(int a){
printf(" Base :: gogo (int) \n");
};
virtual void gogo(int* a){
printf(" Base :: gogo (int*) \n");
};
};
class Derived : public Base{
public:
virtual void gogo(int* a){
printf(" Derived :: gogo (int*) \n");
};
};
int main(){
Derived obj;
obj.gogo(7);
}
Habe diesen Fehler bekommen:
> g ++ -pedantic -Os test.cpp -o test test.cpp: In der Funktion `int main () ': test.cpp: 31: Fehler: Keine passende Funktion für den Aufruf von `Derived :: gogo (int) ' test.cpp: 21: Hinweis: Kandidaten sind: virtual void Derived :: gogo (int *) test.cpp: 33: 2: Warnung: Kein Zeilenumbruch am Dateiende > Exit-Code: 1
Hier verdunkelt die Funktion der abgeleiteten Klasse alle gleichnamigen Funktionen (keine Signatur) in der Basisklasse. Irgendwie sieht dieses Verhalten von C ++ nicht in Ordnung aus. Nicht polymorph.
c++
polymorphism
overriding
Aman Aggarwal
quelle
quelle
obj.Base::gogo(7);
immer noch funktioniert, indem man die versteckte Funktion aufruft.Antworten:
Nach dem Wortlaut Ihrer Frage zu urteilen (Sie haben das Wort "verstecken" verwendet) wissen Sie bereits, was hier vor sich geht. Das Phänomen wird "Namensverstecken" genannt. Aus irgendeinem Grund sagen die Antwortenden jedes Mal, wenn jemand eine Frage stellt, warum das Verstecken von Namen erfolgt, dass dies als "Verstecken von Namen" bezeichnet wird, und erklären, wie es funktioniert (was Sie wahrscheinlich bereits wissen), oder erklären, wie Sie es überschreiben (was Sie tun) nie gefragt), aber niemand scheint sich darum zu kümmern, die eigentliche "Warum" -Frage anzusprechen.
Die Entscheidung, die Begründung für das Ausblenden des Namens, dh warum er tatsächlich in C ++ entwickelt wurde, besteht darin, bestimmte kontraintuitive, unvorhergesehene und potenziell gefährliche Verhaltensweisen zu vermeiden, die auftreten könnten, wenn der geerbte Satz überladener Funktionen mit dem aktuellen Satz von gemischt werden könnte Überladungen in der angegebenen Klasse. Sie wissen wahrscheinlich, dass die Überlastungsauflösung in C ++ funktioniert, indem Sie die beste Funktion aus der Gruppe der Kandidaten auswählen. Dies erfolgt durch Abgleichen der Argumenttypen mit den Parametertypen. Die Übereinstimmungsregeln können manchmal kompliziert sein und oft zu Ergebnissen führen, die von einem unvorbereiteten Benutzer als unlogisch empfunden werden. Das Hinzufügen neuer Funktionen zu einer Reihe zuvor vorhandener Funktionen kann zu einer drastischen Verschiebung der Ergebnisse der Überlastungsauflösung führen.
Angenommen, die Basisklasse
B
verfügt über eine Elementfunktionfoo
, die einen Parameter vom Typ akzeptiertvoid *
, und alle aufrufenden Aufrufefoo(NULL)
werden aufgelöstB::foo(void *)
. Nehmen wir an, es gibt keinen Namen, der sich versteckt, und diesB::foo(void *)
ist in vielen verschiedenen Klassen sichtbar, die von abstammenB
. Nehmen wir jedoch an, in einem [indirekten, entfernten] NachkommenD
der Klasse istB
eine Funktionfoo(int)
definiert. Nun, ohne Namen verstecktD
hat beidefoo(void *)
undfoo(int)
sichtbar und die Teilnahme an der Überladungsauflösung. Zu welcher Funktion werden die Aufrufe aufgelöstfoo(NULL)
, wenn sie über ein Objekt vom Typ ausgeführt werdenD
? Sie werden sich auflösenD::foo(int)
, da diesint
eine bessere Übereinstimmung für das Integral Null ist (dhNULL
) als jeder Zeigertyp. Während der gesamten Hierarchie werden Aufrufe zurfoo(NULL)
Auflösung in eine Funktion ausgeführt, während sie inD
(und unter) plötzlich in eine andere aufgelöst werden.Ein weiteres Beispiel finden Sie in Das Design und die Entwicklung von C ++ , Seite 77:
Ohne diese Regel würde der Status von b teilweise aktualisiert, was zu einer Aufteilung führen würde.
Dieses Verhalten wurde bei der Gestaltung der Sprache als unerwünscht angesehen. Als besserer Ansatz wurde beschlossen, die Spezifikation "Name verstecken" zu befolgen, was bedeutet, dass jede Klasse in Bezug auf jeden deklarierten Methodennamen mit einem "sauberen Blatt" beginnt. Um dieses Verhalten zu überschreiben, muss der Benutzer eine explizite Aktion ausführen: ursprünglich eine erneute Deklaration geerbter Methoden (derzeit veraltet), jetzt eine explizite Verwendung der using-Deklaration.
Wie Sie in Ihrem ursprünglichen Beitrag richtig festgestellt haben (ich beziehe mich auf die Bemerkung "Nicht polymorph"), kann dieses Verhalten als Verstoß gegen die IS-A-Beziehung zwischen den Klassen angesehen werden. Dies ist wahr, aber anscheinend wurde damals entschieden, dass sich das Verstecken von Namen am Ende als weniger böse erweisen würde.
quelle
nullptr
würde ich Ihrem Beispiel widersprechen, indem ich sage: "Wenn Sie dievoid*
Version aufrufen möchten , sollten Sie einen Zeigertyp verwenden." Gibt es ein anderes Beispiel, wo dies schlecht sein kann?d->foo()
bekommen Sie das "Is-aBase
" nicht, aber esstatic_cast<Base*>(d)->foo()
wird , einschließlich des dynamischen Versands.Die Regeln für die Namensauflösung besagen, dass die Namenssuche im ersten Bereich stoppt, in dem ein übereinstimmender Name gefunden wird. Zu diesem Zeitpunkt werden die Regeln für die Überlastungsauflösung aktiviert, um die beste Übereinstimmung der verfügbaren Funktionen zu finden.
In diesem Fall
gogo(int*)
wird (allein) im Bereich der abgeleiteten Klasse gefunden, und da es keine Standardkonvertierung von int nach int * gibt, schlägt die Suche fehl.Die Lösung besteht darin, die Basisdeklarationen über eine using-Deklaration in der Klasse Derived einzufügen:
... würde es den Regeln für die Namenssuche ermöglichen, alle Kandidaten zu finden, und somit würde die Überlastungsauflösung wie erwartet verlaufen.
quelle
Dies ist "By Design". In C ++ funktioniert die Überlastungsauflösung für diese Art von Methode wie folgt.
Da Derived keine passende Funktion namens "gogo" hat, schlägt die Überlastungsauflösung fehl.
quelle
Das Ausblenden von Namen ist sinnvoll, da es Mehrdeutigkeiten bei der Namensauflösung verhindert.
Betrachten Sie diesen Code:
Wenn dies in Derived
Base::func(float)
nicht verborgenDerived::func(double)
wäre, würden wir beim Aufrufen die Basisklassenfunktion aufrufendobj.func(0.f)
, obwohl ein Float zu einem Double heraufgestuft werden kann.Referenz: http://bastian.rieck.ru/blog/posts/2016/name_hiding_cxx/
quelle