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

StackOverflow https://stackoverflow.com/questions/1628768

  •  06-07-2019
  •  | 
  •  

Frage

Betrachten Sie den Code ein:

#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);
}

Haben Sie 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 die Funktion der abgeleiteten Klasse Bedeckungs alle Funktionen gleichen Namen (nicht Signatur) in der Basisklasse. Irgendwie ist dieses Verhalten von C ++ nicht OK aussehen. Nicht polymorph.

War es hilfreich?

Lösung

Gemessen an der Formulierung Ihrer Frage (Sie verwendet das Wort „verstecken“), wissen Sie bereits, was hier vor sich geht. Das Phänomen wird als „Namen Versteck“ genannt. Aus irgendeinem Grund fragt jedes Mal, wenn jemand eine Frage zu Warum Namen versteckt geschieht, Menschen, die reagieren entweder sagen, dass dieses „name Versteck“ und erklären, wie es funktioniert (was Sie wahrscheinlich bereits wissen), oder erklären, wie es außer Kraft zu setzen (die Sie nie gefragt), aber niemand scheint zu kümmern die eigentliche „warum“ Frage zu beantworten.

Die Entscheidung, die Gründe hinter dem Namen versteckt, dh Warum es wurde eigentlich entwickelt, um in C ++, ist sicher nicht eingängig, unvorhergesehen und potenziell gefährliches Verhalten zu vermeiden, die stattfinden könnten, wenn die geerbte Gruppe überladenen Funktionen durften mit dem aktuellen Satz von Überlastungen in der gegebenen Klasse mischen. Sie wissen wahrscheinlich, dass in C ++ Überladungsauflösung funktioniert, indem die beste Funktion aus der Gruppe von Kandidaten wählen. Dies wird durch die Anpassung der Arten von Argumenten zu den Typen von Parametern durchgeführt. Die passenden Regeln zu Zeiten werden könnten kompliziert und oft zu Ergebnissen führen, die als unlogisch von einem unvorbereiteten Benutzer wahrgenommen werden könnten. Das Hinzufügen von neuen Funktionen zu einem Satz von vorher bestehenden führen könnte in einer ziemlich drastischen Verschiebung in der Überladungsauflösung Ergebnissen.

Zum Beispiel, sagen wir mal die Basisklasse B eine Memberfunktion foo hat, die einen Parameter vom Typ void * nimmt, und alle Anrufe sind entschlossen, foo(NULL) zu B::foo(void *). Sagen wir, es gibt keinen Namen versteckt und diese B::foo(void *) ist sichtbar in vielen verschiedenen Klassen von B absteigend. Allerdings sagen wir mal in einigen [indirekt, remote] Nachkomme D der Klasse B eine Funktion foo(int) definiert ist. Nun, ohne Namen D versteckt hat sowohl foo(void *) und foo(int) sichtbar und die Teilnahme an der Überladungsauflösung. Welche Funktion werden die Anrufe foo(NULL) Entschlossenheit, wenn durch ein Objekt vom Typ D gemacht? Sie werden lösen D::foo(int), da int eine bessere Übereinstimmung für Integral Null ist (das heißt NULL) als jeder Zeigertyp. Also, in der gesamten Hierarchie fordert Entschlossenheit, eine Funktion foo(NULL), während in D (und unter) sie plötzlich zu einem anderen lösen.

Ein weiteres Beispiel findet sich in The Design und Entwicklung von C ++ , Seite 77:

class Base {
    int x;
public:
    virtual void copy(Base* p) { x = p-> x; }
};

class Derived{
    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 b des Staates würde Slicing teilweise aktualisiert werden, führt.

Dieses Verhalten gilt als unerwünscht, wenn die Sprache entwickelt wurde. Als besseren Ansatz wurde beschlossen, die „Name versteckt“ Spezifikation zu folgen, was bedeutet, dass jede Klasse beginnt mit einem „sauberen Blatt“ in Bezug auf jeden Methodennamen erklärt sie. Um dieses Verhalten zu ändern, eine explizite Aktion von dem Benutzer benötigt: ursprünglich ein Neudeklaration der geerbten Methode (n) (zur Zeit veraltet), jetzt eine explizite Verwendung von using-Deklaration

.

Wie Sie richtig in Ihrer ursprünglichen Post beobachtet (Ich beziehe mich auf die „nicht polymorph“ Bemerkung), könnte dieses Verhalten als eine Verletzung der IS-A relationsip zwischen den Klassen zu sehen. Das ist wahr, aber anscheinend damals wurde beschlossen, dass am Ende Namen Versteck beweisen würde ein kleineres Übel sein.

Andere Tipps

Die Namensauflösung Regeln sagen, dass Namen-Suche in der ersten Umfang hält, in dem ein passender Name gefunden wird. An diesem Punkt treten die Überladungsauflösung Regeln in die beste Übereinstimmung der verfügbaren Funktionen zu finden.

In diesem Fall gogo(int*) wird (allein) in der abgeleiteten Klasse Bereich gefunden, und da es von int keine Standardkonvertierung ist * int, schlägt der Nachschlag.

Die Lösung ist die Basis Erklärungen über eine using-Deklaration in der abgeleiteten Klasse zu bringen:

using Base::gogo;

... würden die Namen-Suche Regeln erlauben alle Kandidaten zu finden und somit würde die Überladungsauflösung gehen Sie wie erwartet.

Das ist "By Design". In C ++ Überladungsauflösung für diese Art von Verfahren funktioniert wie folgt aus.

  • Ab der Art der Referenz und dann in den Basistyp gehen, finden die erste Art, die ein Verfahren „gogo“
  • genannt hat
  • Betrachtet man nur Methoden mit dem Namen „gogo“ auf diese Art eine passende Überlastung finden

Da Abgeleitet hat keine passende Funktion mit dem Namen „gogo“, schlägt die Überladungsauflösung.

Name Versteck macht Sinn, weil es Unklarheiten bei der Namensauflösung verhindert.

Betrachten Sie diesen Code ein:

class Base
{
public:
    void func (float x) { ... }
}

class Derived: public Base
{
public:
    void func (double x) { ... }
}

Derived dobj;

Wenn Base::func(float) nicht durch Derived::func(double) in Abgeleitet versteckt war, würden wir die Funktion der Basisklasse aufrufen, wenn dobj.func(0.f) Aufruf, auch wenn ein Schwimmer kann zu einem Doppel gefördert werden.

Referenz: http://bastian.rieck.ru/blog/posts/ 2016 / name_hiding_cxx /

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top