Warum verbirgt eine überschriebene Funktion in der abgeleiteten Klasse andere Überladungen der Basisklasse?

Lesezeit: 7 Minuten

Warum verbirgt eine uberschriebene Funktion in der abgeleiteten Klasse andere
Aman Aggarwal

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.

  • 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

1647265809 93 Warum verbirgt eine uberschriebene Funktion in der abgeleiteten Klasse andere
Ameise

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.

  • Ja, das ist eine echte Antwort auf die Frage. Danke. Ich war auch neugierig.

    – Allmächtig

    27. Oktober 2009 um 6:55 Uhr

  • Gute Antwort! Außerdem würde die Kompilierung aus praktischen Gründen wahrscheinlich viel langsamer werden, wenn die Namenssuche jedes Mal ganz nach oben gehen müsste.

    – Zeichnete Hall

    27. Oktober 2009 um 11:24 Uhr

  • (Alte Antwort, ich weiß.) Jetzt wird nullptr Ich würde Ihrem Beispiel widersprechen, indem ich sagte: “Wenn Sie anrufen wollten void* Version sollten Sie einen Zeigertyp verwenden”. Gibt es ein anderes Beispiel, wo dies schlecht sein kann?

    – GManNickG

    17. Mai 2011 um 19:26 Uhr


  • Das Namensverstecken ist nicht wirklich böse. Die „ist-ein“-Beziehung ist immer noch vorhanden und über die Basisschnittstelle verfügbar. Also vielleicht d->foo() wird dir das “Is-a” nicht bringen Base“, aber static_cast<Base*>(d)->foo() Willeeinschließlich dynamischem Versand.

    – Kerrek SB

    9. Januar 2014 um 13:36 Uhr

  • Diese Antwort ist nicht hilfreich, da sich das angegebene Beispiel mit oder ohne Verstecken gleich verhält: D::foo(int) wird entweder aufgerufen, weil es eine bessere Übereinstimmung ist oder weil es B:foo(int) ausgeblendet hat.

    – Richard Wolf

    8. Juli 2014 um 3:06 Uhr

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.

  • OP: „Warum verbirgt eine überschriebene Funktion in der abgeleiteten Klasse andere Überladungen der Basisklasse?“ Diese Antwort: “Weil es so ist”.

    – Richard Wolf

    8. Juli 2014 um 3:58 Uhr


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/

1001850cookie-checkWarum verbirgt eine überschriebene Funktion in der abgeleiteten Klasse andere Überladungen der Basisklasse?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy