Considera el código:
#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);
}
Tengo este error:
> g ++ -pedantic -Os test.cpp -o test test.cpp: en la función `int main () ': test.cpp: 31: error: no hay función coincidente para la llamada a `Derived :: gogo (int) ' test.cpp: 21: nota: los candidatos son: virtual void Derived :: gogo (int *) test.cpp: 33: 2: advertencia: no hay nueva línea al final del archivo > Código de salida: 1
Aquí, la función de la clase Derivada está eclipsando todas las funciones del mismo nombre (no firma) en la clase base. De alguna manera, este comportamiento de C ++ no se ve bien. No polimórfico
c++
polymorphism
overriding
Aman Aggarwal
fuente
fuente
obj.Base::gogo(7);
todavía funciona llamando a la función oculta.Respuestas:
A juzgar por la redacción de su pregunta (usó la palabra "ocultar"), ya sabe lo que está sucediendo aquí. El fenómeno se llama "ocultación de nombres". Por alguna razón, cada vez que alguien hace una pregunta sobre por qué ocurre la ocultación del nombre, las personas que responden dicen que esto se llama "ocultación del nombre" y explican cómo funciona (lo que probablemente ya sepas), o explican cómo anularlo (lo cual nunca se le preguntó), pero a nadie parece importarle abordar la pregunta real del "por qué".
La decisión, la razón detrás de la ocultación del nombre, es decir, por qué en realidad se diseñó en C ++, es evitar ciertos comportamientos contraintuitivos, imprevistos y potencialmente peligrosos que podrían tener lugar si se permitiera que el conjunto heredado de funciones sobrecargadas se mezclara con el conjunto actual de sobrecargas en la clase dada. Probablemente sepa que en C ++ la resolución de sobrecarga funciona eligiendo la mejor función del conjunto de candidatos. Esto se realiza haciendo coincidir los tipos de argumentos con los tipos de parámetros. Las reglas de coincidencia pueden ser complicadas a veces, y a menudo conducen a resultados que pueden ser percibidos como ilógicos por un usuario no preparado. Agregar nuevas funciones a un conjunto de funciones previamente existentes podría resultar en un cambio bastante drástico en los resultados de resolución de sobrecarga.
Por ejemplo, supongamos que la clase base
B
tiene una función miembrofoo
que toma un parámetro de tipovoid *
yfoo(NULL)
se resuelven todas las llamadas aB::foo(void *)
. Digamos que no hay nombre oculto y estoB::foo(void *)
es visible en muchas clases diferentes que desciendenB
. Sin embargo, supongamos que en algún descendiente [indirecto, remoto]D
de la clase se defineB
una funciónfoo(int)
. Ahora, sin ocultar el nombreD
tiene tantofoo(void *)
yfoo(int)
visible y participando en la resolución de sobrecarga. ¿A qué función sefoo(NULL)
resolverán las llamadas , si se realizan a través de un objeto de tipoD
? ResolveránD::foo(int)
, desdeint
es una mejor coincidencia para el cero integral (es decirNULL
) que cualquier tipo de puntero. Entonces, a lo largo de la jerarquía, las llamadas sefoo(NULL)
resolver a una función, mientras que enD
(y debajo) de repente se resuelven a otra.Otro ejemplo se da en The Design and Evolution of C ++ , página 77:
Sin esta regla, el estado de b se actualizaría parcialmente, lo que llevaría a la división.
Este comportamiento se consideró indeseable cuando se diseñó el lenguaje. Como un mejor enfoque, se decidió seguir la especificación de "ocultación del nombre", lo que significa que cada clase comienza con una "hoja limpia" con respecto a cada nombre de método que declara. Para anular este comportamiento, se requiere una acción explícita del usuario: originalmente una redeclaración de los métodos heredados (actualmente en desuso), ahora un uso explícito del uso de la declaración.
Como observó correctamente en su publicación original (me refiero al comentario "No polimórfico"), este comportamiento podría verse como una violación de la relación IS-A entre las clases. Esto es cierto, pero aparentemente en aquel entonces se decidió que, al final, esconderse resultaría ser un mal menor.
fuente
nullptr
me opondría a su ejemplo diciendo "si desea llamar a lavoid*
versión, debe usar un tipo de puntero". ¿Hay un ejemplo diferente donde esto puede ser malo?d->foo()
vez no le dé el "Is-aBase
", pero lostatic_cast<Base*>(d)->foo()
hará , incluido el despacho dinámico.Las reglas de resolución de nombres dicen que la búsqueda de nombres se detiene en el primer ámbito en el que se encuentra un nombre coincidente. En ese punto, las reglas de resolución de sobrecarga entran en acción para encontrar la mejor combinación de funciones disponibles.
En este caso,
gogo(int*)
se encuentra (solo) en el ámbito de la clase Derivada, y como no hay una conversión estándar de int a int *, la búsqueda falla.La solución es traer las declaraciones de Base a través de una declaración de uso en la clase Derivada:
... permitiría que las reglas de búsqueda de nombres encuentren todos los candidatos y, por lo tanto, la resolución de sobrecarga continuaría como esperaba.
fuente
Esto es "Por diseño". En C ++, la resolución de sobrecarga para este tipo de método funciona de la siguiente manera.
Dado que Derived no tiene una función de coincidencia llamada "gogo", la resolución de sobrecarga falla.
fuente
La ocultación de nombres tiene sentido porque evita ambigüedades en la resolución de nombres.
Considera este código:
Si
Base::func(float)
no estuviera ocultoDerived::func(double)
en Derivado, llamaríamos a la función de la clase base al llamardobj.func(0.f)
, aunque un float pueda promoverse a un doble.Referencia: http://bastian.rieck.ru/blog/posts/2016/name_hiding_cxx/
fuente