¿Por qué una función anulada en la clase derivada oculta otras sobrecargas de la clase base?

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

  •  06-07-2019
  •  | 
  •  

Pregunta

Considere 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: 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

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.

¿Fue útil?

Solución

A juzgar por la redacción de su pregunta (utilizó 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 explica cómo funciona (lo que probablemente ya sabes), o explica cómo anularlo (sobre lo que nunca preguntaste), pero a nadie parece importarle abordar el verdadero "por qué" pregunta.

La decisión, la razón detrás del ocultamiento 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 el conjunto heredado de sobrecargado Se permitió que las funciones se mezclaran con el conjunto actual de sobrecargas en la clase dada. Probablemente sepa que en C ++ la resolución de sobrecarga funciona al elegir 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 existentes anteriormente 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 miembro foo que toma un parámetro de tipo void * , y todas las llamadas a foo (NULL) se resuelven a B :: foo (void *) . Digamos que no hay nombre oculto y este B :: foo (void *) es visible en muchas clases diferentes que descienden de B . Sin embargo, supongamos que en algunos D descendientes [indirectos, remotos] de la clase B se define una función foo (int) . Ahora, sin ocultar el nombre, D tiene foo (void *) y foo (int) visibles y participando en la resolución de sobrecarga. ¿Qué función resolverán las llamadas a foo (NULL) , si se realizan a través de un objeto de tipo D ? Resolverán a D :: foo (int) , ya que int es una mejor coincidencia para el cero integral (es decir, NULL ) que cualquier tipo de puntero . Entonces, a lo largo de la jerarquía, las llamadas a foo (NULL) resuelven una función, mientras que en D (y debajo) de repente se resuelven a otra.

Otro ejemplo se da en El diseño y la evolución de C ++ , página 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*)
}

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 el "ocultamiento del nombre". especificación, 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 a la observación "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.

Otros consejos

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 momento, 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:

using Base::gogo;

... permitiría que las reglas de búsqueda de nombres encuentren a todos los candidatos y, por lo tanto, la resolución de sobrecarga continuaría como esperaba.

Esto es " Por diseño " ;. En C ++, la resolución de sobrecarga para este tipo de método funciona de la siguiente manera.

  • Comenzando en el tipo de referencia y luego yendo al tipo base, encuentre el primer tipo que tiene un método llamado " gogo "
  • Considerando solo los métodos denominados "gogo" en ese tipo encuentre una sobrecarga coincidente

Dado que Derived no tiene una función de coincidencia denominada " gogo " ;, falla la resolución de sobrecarga.

La ocultación de nombres tiene sentido porque evita ambigüedades en la resolución de nombres.

Considere este código:

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

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

Derived dobj;

Si Base :: func (float) no estaba oculto por Derived :: func (double) en Derived, llamaríamos a la función de clase base al llamar a dobj.func (0.f) , a pesar de que un float puede promoverse a un doble.

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top