Función con el mismo nombre pero firma diferente en clase derivada

92

Tengo una función con el mismo nombre, pero con diferente firma en una base y clases derivadas. Cuando intento usar la función de la clase base en otra clase que hereda de la derivada, recibo un error. Vea el siguiente código:

class A
{
    public:
    void foo(string s){};
};

class B : public A
{
    public:
    int foo(int i){};
};

class C : public B
{
    public:
    void bar()
    {
        string s;
        foo(s);
    }
};

Recibo el siguiente error del compilador gcc:

In member function `void C::bar()': no matching function for call to `C::foo(std::string&)' candidates are: int B::foo(int)

Si elimino int foo(int i){};de la clase B, o si le cambio el nombre foo1, todo funciona bien.

Cual es el problema con esto?

Igor Oks
fuente
1
Técnicamente un duplicado de esta pregunta, pero esta tiene un mejor título y respuestas.
Trovador

Respuestas:

79

Las funciones en clases derivadas que no anulan funciones en clases base pero que tienen el mismo nombre ocultarán otras funciones con el mismo nombre en la clase base.

En general, se considera una mala práctica tener funciones en clases derivadas que tengan el mismo nombre que las funciones en la clase de graves que no pretenden anular las funciones de la clase base, ya que lo que está viendo no suele ser un comportamiento deseable. Por lo general, es preferible dar nombres diferentes a funciones diferentes.

Si necesita llamar a la función base, deberá establecer el alcance de la llamada utilizando A::foo(s). Tenga en cuenta que esto también deshabilitaría cualquier mecanismo de función virtual A::foo(string)al mismo tiempo.

CB Bailey
fuente
13
también lea la respuesta de litdb: puede 'mostrar' la función base mediante una cláusula 'usando A :: foo' en B.
xtofl
Es cierto, solo estaba buscando una solución que pudiera usarse en el sitio de la llamada, tratando la jerarquía base como fija.
CB Bailey
2
¿Cuál es la base de esta afirmación y sigue el consejo: "En general, se considera una mala práctica tener funciones en clases derivadas que tengan el mismo nombre que funciones en la clase de graves que no pretenden anular las funciones de la clase base como lo que está viendo no suele ser un comportamiento deseable. Suele ser preferible dar nombres diferentes a las diferentes funciones " . ¿Qué pasa si están haciendo lo mismo semánticamente? Sin embargo, C ++ le proporciona una solución al problema causado por esto, como se explica en la respuesta de Johannes.
Nawaz
109

Es porque la búsqueda de nombres se detiene si encuentra un nombre en una de sus bases. No mirará más allá de otras bases. La función en B sombrea la función en A. Tienes que volver a declarar la función de A en el alcance de B, de modo que ambas funciones sean visibles desde B y C:

class A
{
    public:
    void foo(string s){};
};

class B : public A
{
    public:
    int foo(int i){};
    using A::foo;
};

class C : public B
{
    public:
    void bar()
    {
        string s;
        foo(s);
    }
};

Editar: La descripción real que da el Estándar es (de 10.2 / 2):

Los siguientes pasos definen el resultado de la búsqueda de nombres en el ámbito de una clase, C. Primero, se considera cada declaración del nombre en la clase y en cada uno de sus subobjetos de clase base. Un nombre de miembro f en un subobjeto B oculta un nombre de miembro f en un subobjeto A si A es un subobjeto de clase base de B. Cualquier declaración que esté tan oculta se elimina de consideración. Cada una de estas declaraciones que fue introducida por una declaración-using se considera que es de cada subobjeto de C que es del tipo que contiene la declaración designada por la declaración-using.96) Si el conjunto resultante de declaraciones no es todos de subobjetos del mismo tipo, o el conjunto tiene un miembro no estático e incluye miembros de distintos subobjetos, existe una ambigüedad y el programa está mal formado. De lo contrario, ese conjunto es el resultado de la búsqueda.

Tiene lo siguiente que decir en otro lugar (justo encima):

Para una expresión-id [ algo como "foo" ], la búsqueda de nombre comienza en el ámbito de la clase de this; para un id-calificado [ algo así como "A :: foo", A es un especificador-nombre-anidado ], la búsqueda de nombre comienza en el alcance del especificador-nombre-anidado. La búsqueda de nombres se realiza antes del control de acceso (3.4, cláusula 11).

([...] puesto por mí). Tenga en cuenta que eso significa que incluso si su foo en B es privado, el foo en A todavía no se encontrará (porque el control de acceso ocurre más tarde).

Johannes Schaub - litb
fuente
litb, gracias por tu respuesta. Pero cuando trato de compilar su código, obtengo: no se puede ajustar el acceso a la void A::foo(class basic_string<char,char_traits<char>,allocator<char> >)' in clase B 'debido al método local' int B :: foo (int) 'con el mismo nombre. Tal vez sea porque uso una versión antigua de gcc
Igor Oks
1
sí, definitivamente un error del compilador. Los compiladores antiguos solían usar "A :: foo;" en lugar de "usar A :: foo;" pero el primero está en desuso en C ++.
Johannes Schaub - litb