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:
>g++ -pedantic -Os test.cpp -o test
test.cpp: In function `int main()':
test.cpp:31: error: no matching function for call to `Derived::gogo(int)'
test.cpp:21: note: candidates are: virtual void Derived::gogo(int*)
test.cpp:33:2: warning: no newline at end of file
>Exit code: 1
Hier verdunkelt die Funktion der abgeleiteten Klasse alle Funktionen mit demselben Namen (nicht Signatur) in der Basisklasse. Irgendwie sieht dieses Verhalten von C++ nicht in Ordnung aus. Nicht polymorph.
Dem Wortlaut Ihrer Frage nach zu urteilen (Sie haben das Wort “verstecken” verwendet), wissen Sie bereits, was hier vor sich geht. Das Phänomen wird “Name Hiding” genannt. Aus irgendeinem Grund stellt jedes Mal jemand eine Frage über warum Name Hiding passiert, Leute, die antworten, sagen entweder, dass dies “Name Hiding” genannt wird, und erklären, wie es funktioniert (was Sie wahrscheinlich bereits wissen), oder erklären, wie man es überschreibt (wonach Sie nie gefragt haben), aber niemand scheint sich darum zu kümmern die eigentliche Warum-Frage.
Die Entscheidung, die Begründung hinter dem Namen zu verbergen, dh warum Es wurde tatsächlich in C++ entworfen, um bestimmte kontraintuitive, unvorhergesehene und potenziell gefährliche Verhaltensweisen zu vermeiden, die auftreten könnten, wenn zugelassen würde, dass sich der geerbte Satz überladener Funktionen mit dem aktuellen Satz von Überladungen in der angegebenen Klasse vermischt. Sie wissen wahrscheinlich, dass die Überladungsauflösung in C++ funktioniert, indem Sie die beste Funktion aus der Menge der Kandidaten auswählen. Dies geschieht durch Anpassen der Typen von Argumenten an die Typen von Parametern. Die Zuordnungsregeln können manchmal kompliziert sein und führen oft zu Ergebnissen, die von einem unvorbereiteten Benutzer als unlogisch empfunden werden könnten. Das Hinzufügen neuer Funktionen zu einem Satz bereits bestehender Funktionen kann zu einer ziemlich drastischen Verschiebung der Ergebnisse der Überladungsauflösung führen.
Sagen wir zum Beispiel die Basisklasse B
hat eine Mitgliedsfunktion foo
das nimmt einen Parameter vom Typ void *
und alle Anrufe an foo(NULL)
sind dazu entschlossen B::foo(void *)
. Nehmen wir an, es versteckt sich kein Name und das B::foo(void *)
ist in vielen verschiedenen Klassen absteigend sichtbar B
. Sagen wir jedoch in einigen [indirect, remote] Nachfahre D
der Klasse B
eine Funktion foo(int)
ist definiert. Jetzt ohne Namensversteck D
hat beide foo(void *)
und foo(int)
sichtbar und beteiligt an der Auflösung von Überlastungen. Zu welcher Funktion werden die Aufrufe foo(NULL)
auflösen, wenn es durch ein Objekt des Typs gemacht wird D
? Sie werden sich dazu entschließen D::foo(int)
seit int
passt besser zum ganzzahligen Nullpunkt (d. h NULL
) als jeder Zeigertyp. Also, in der gesamten Hierarchie ruft nach foo(NULL)
auf eine Funktion auflösen, während in D
(und darunter) lösen sie sich plötzlich zu einem anderen auf.
Ein weiteres Beispiel findet sich in Das Design und die Entwicklung von C++Seite 77:
class Base {
int x;
public:
virtual void copy(Base* p) { x = p-> x; }
};
class Derived : public Base{
int xx;
public:
virtual void copy(Derived* p) { xx = p->xx; Base::copy(p); }
};
void f(Base a, Derived b)
{
a.copy(&b); // ok: copy Base part of b
b.copy(&a); // error: copy(Base*) is hidden by copy(Derived*)
}
Ohne diese Regel würde der Zustand von b teilweise aktualisiert, was zu einem Slicing führen würde.
Dieses Verhalten wurde bei der Entwicklung der Sprache als unerwünscht angesehen. Als besseren Ansatz wurde entschieden, der „Name Hiding“-Spezifikation zu folgen, was bedeutet, dass jede Klasse mit einem „clean sheet“ in Bezug auf jeden von ihr deklarierten Methodennamen beginnt. Um dieses Verhalten zu überschreiben, ist eine explizite Aktion des Benutzers erforderlich: ursprünglich eine erneute Deklaration von geerbten 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”), könnte dieses Verhalten als Verletzung der IS-A-Beziehung zwischen den Klassen angesehen werden. Das stimmt, aber anscheinend wurde damals entschieden, dass sich das Verbergen von Namen am Ende als kleineres Übel erweisen würde.
Die Namensauflösungsregeln besagen, dass die Namenssuche im ersten Bereich stoppt, in dem ein übereinstimmender Name gefunden wird. An diesem Punkt greifen die Überladungsauflösungsregeln, 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 Base-Deklarationen über eine using-Deklaration in die Derived-Klasse einzubringen:
using Base::gogo;
…würde es den Namenssuchregeln ermöglichen, alle Kandidaten zu finden, und somit würde die Überladungsauflösung wie erwartet fortgesetzt.
Dies ist “durch Design”. In C++ funktioniert die Überladungsauflösung für diesen Methodentyp wie folgt.
- Beginnen Sie mit dem Typ der Referenz und gehen Sie dann zum Basistyp, suchen Sie den ersten Typ, der eine Methode namens “gogo” hat.
- Wenn man bedenkt, dass nur Methoden mit dem Namen “gogo” für diesen Typ eine passende Überladung finden
Da Derived keine passende Funktion namens „gogo“ hat, schlägt die Überladungsauflösung fehl.
Name Hiding ist sinnvoll, weil es Mehrdeutigkeiten bei der Namensauflösung verhindert.
Betrachten Sie diesen Code:
class Base
{
public:
void func (float x) { ... }
}
class Derived: public Base
{
public:
void func (double x) { ... }
}
Derived dobj;
Wenn Base::func(float)
wurde nicht von verdeckt Derived::func(double)
in Derived würden wir beim Aufruf die Basisklassenfunktion aufrufen dobj.func(0.f)
obwohl ein Float zu einem Double befördert werden kann.
Referenz: http://bastian.rieck.ru/blog/posts/2016/name_hiding_cxx/
Duplizieren: stackoverflow.com/questions/411103/…
– psychotik
27. Oktober 2009 um 4:30 Uhr
geniale Frage, das habe ich auch erst vor kurzem entdeckt
– Matt Tischler
27. Oktober 2009 um 7:30 Uhr
Ich denke, Bjarne (aus dem von Mac geposteten Link) hat es am besten in einem Satz ausgedrückt: “In C++ gibt es keine Überladung über Bereiche hinweg – abgeleitete Klassenbereiche sind keine Ausnahme von dieser allgemeinen Regel.”
– Shivabudh
11. Februar 2010 um 17:56 Uhr
@Ashish Dieser Link ist defekt. Hier ist die richtige (ab sofort) – stroustrup.com/bs_faq2.html#overloadderived
– verrückt
25. Oktober 2015 um 11:37 Uhr
Wollte auch darauf hinweisen
obj.Base::gogo(7);
funktioniert immer noch durch Aufrufen der versteckten Funktion.– Forumgeber
30. Januar 2018 um 21:19 Uhr