Função com o mesmo nome, mas assinatura diferente na classe derivada

91

Tenho uma função com o mesmo nome, mas com assinatura diferente em classes base e derivadas. Quando estou tentando usar a função da classe base em outra classe que herda da derivada, recebo um erro. Veja o seguinte 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);
    }
};

Recebo o seguinte erro do compilador gcc:

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

Se eu remover int foo(int i){};da classe Bou renomear foo1, tudo funcionará bem.

Qual é o problema disso?

Igor Oks
fonte
1
Tecnicamente, uma duplicata desta pergunta, mas esta tem um título e respostas melhores.
Troubadour de

Respostas:

77

Funções em classes derivadas que não substituem funções em classes base, mas que têm o mesmo nome, irão ocultar outras funções com o mesmo nome na classe base.

É geralmente considerado uma prática ruim ter funções em classes derivadas que têm o mesmo nome de funções na classe de baixo, que não têm a intenção de substituir as funções da classe base, pois o que você está vendo geralmente não é o comportamento desejável. Geralmente é preferível dar nomes diferentes a funções diferentes.

Se você precisar chamar a função de base, você precisará definir o escopo da chamada usando A::foo(s). Observe que isso também desabilitaria qualquer mecanismo de função virtual A::foo(string)ao mesmo tempo.

CB Bailey
fonte
13
também leia a resposta de litdb: você pode 'mostrar' a função base por uma cláusula 'usando A :: foo' em B.
xtofl
É verdade, eu estava apenas procurando uma solução que pudesse ser usada no site da chamada, tratando a hierarquia de base como fixa.
CB Bailey
2
Qual é a base desta afirmação e seguida pelo conselho: "É geralmente considerado uma má prática ter funções em classes derivadas que têm o mesmo nome que funções na classe de baixo que não têm a intenção de substituir as funções da classe base como o que você está vendo geralmente não é um comportamento desejável. Normalmente é preferível dar nomes diferentes a funções diferentes " . E se eles estiverem semanticamente fazendo a mesma coisa? C ++ fornece uma solução para o problema causado por isso, porém, conforme explicado pela resposta de Johannes.
Nawaz,
107

É porque a pesquisa de nome para se encontrar um nome em uma de suas bases. Não vai olhar além em outras bases. A função em B obscurece a função em A. Você deve declarar novamente a função de A no escopo de B, de modo que ambas as funções sejam visíveis de dentro de B e 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: A descrição real que o Padrão fornece é (de 10.2 / 2):

As etapas a seguir definem o resultado da pesquisa de nome em um escopo de classe, C. Primeiro, cada declaração para o nome na classe e em cada um de seus subobjetos de classe base é considerada. Um nome de membro f em um subobjeto B oculta um nome de membro f em um subobjeto A se A for um subobjeto de classe base de B. Quaisquer declarações que estejam tão ocultas são eliminadas da consideração. Cada uma dessas declarações que foi introduzida por uma declaração de uso é considerada como sendo de cada subobjeto de C que é do tipo que contém a declaração designada pela declaração de uso. 96) Se o conjunto de declarações resultante não for todos de subobjetos do mesmo tipo, ou o conjunto tem um membro não estático e inclui membros de subobjetos distintos, há uma ambigüidade e o programa está mal formado. Caso contrário, esse conjunto é o resultado da pesquisa.

Tem o seguinte a dizer em outro lugar (logo acima):

Para uma expressão de id [ algo como "foo" ], a pesquisa de nome começa no escopo de classe deste; para um id qualificado [ algo como "A :: foo", A é um especificador de nome aninhado ], a pesquisa de nome começa no escopo do especificador de nome aninhado. A pesquisa de nome ocorre antes do controle de acesso (3.4, cláusula 11).

([...] colocado por mim). Note que isso significa que mesmo se seu foo em B for privado, o foo em A ainda não será encontrado (porque o controle de acesso acontece mais tarde).

Johannes Schaub - litb
fonte
litb, obrigado pela sua resposta. Mas quando tento compilar seu código, recebo: não é possível ajustar o acesso à void A::foo(class basic_string<char,char_traits<char>,allocator<char> >)' in classe B 'por causa do método local `int B :: foo (int)' com o mesmo nome. Talvez seja porque eu uso uma versão antiga do gcc
Igor Oks
1
sim, definitivamente um bug do compilador. compiladores antigos costumavam usar "A :: foo;" em vez de "usando A :: foo;" mas o primeiro está obsoleto em C ++.
Johannes Schaub - litb