Considere o 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);
}
Tem este erro:
> g ++ -pedantic -Os test.cpp -o test test.cpp: Na função `int main () ': test.cpp: 31: error: nenhuma função correspondente à chamada para `Derived :: gogo (int) ' test.cpp: 21: note: os candidatos são: virtual void Derivado :: gogo (int *) test.cpp: 33: 2: aviso: nenhuma nova linha no final do arquivo > Código de saída: 1
Aqui, a função da classe Derived está eclipsando todas as funções do mesmo nome (não assinatura) na classe base. De alguma forma, esse comportamento do C ++ não parece bom. Não polimórfico.
c++
polymorphism
overriding
Aman Aggarwal
fonte
fonte
obj.Base::gogo(7);
ainda funciona chamando a função oculta.Respostas:
A julgar pelo teor da sua pergunta (você usou a palavra "ocultar"), você já sabe o que está acontecendo aqui. O fenômeno é chamado de "ocultação de nome". Por alguma razão, sempre que alguém faz uma pergunta sobre por que a ocultação de nomes acontece, as pessoas que respondem dizem que isso se chama "ocultação de nomes" e explicam como funciona (o que você provavelmente já conhece) ou explicam como substituí-lo (que você nunca perguntei sobre), mas ninguém parece se importar em abordar a questão real "por que".
A decisão, a lógica por trás do nome oculto, ou seja, por que ele realmente foi projetado em C ++, é evitar certos comportamentos contra-intuitivos, imprevistos e potencialmente perigosos que possam ocorrer se o conjunto herdado de funções sobrecarregadas puder se misturar ao conjunto atual de sobrecargas na classe especificada. Você provavelmente sabe que na resolução de sobrecarga do C ++ funciona escolhendo a melhor função do conjunto de candidatos. Isso é feito combinando os tipos de argumentos aos tipos de parâmetros. As regras de correspondência podem ser complicadas às vezes e geralmente levam a resultados que podem ser vistos como ilógicos por um usuário despreparado. Adicionar novas funções a um conjunto de funções existentes anteriormente pode resultar em uma mudança bastante drástica nos resultados da resolução de sobrecarga.
Por exemplo, digamos que a classe base
B
tenha uma função de membrofoo
que aceita um parâmetro do tipovoid *
e todas as chamadas parafoo(NULL)
sejam resolvidasB::foo(void *)
. Digamos que não haja ocultação de nomes e issoB::foo(void *)
é visível em várias classes diferentes, descendentes deB
. No entanto, digamos que em algum descendente [indireto, remoto]D
da classeB
uma funçãofoo(int)
seja definida. Agora, sem nome esconderijoD
tem tantofoo(void *)
efoo(int)
visível e participando de resolução de sobrecarga. Para qual função as chamadas serãofoo(NULL)
resolvidas, se feitas por meio de um objeto do tipoD
? Eles resolverãoD::foo(int)
, já queint
é uma melhor correspondência para o zero integral (ou seja,NULL
) que qualquer tipo de ponteiro. Assim, em toda a hierarquia, as chamadas sãofoo(NULL)
resolvidas para uma função, enquanto emD
(e abaixo) elas repentinamente são resolvidas para outra.Outro exemplo é dado em The Design and Evolution of C ++ , página 77:
Sem essa regra, o estado de b seria parcialmente atualizado, levando ao fatiamento.
Esse comportamento foi considerado indesejável quando o idioma foi projetado. Como uma abordagem melhor, foi decidido seguir a especificação "ocultação de nome", o que significa que cada classe começa com uma "planilha" em relação a cada nome de método que declara. Para substituir esse comportamento, é necessária uma ação explícita do usuário: originalmente uma redeclaração de métodos herdados (atualmente obsoletos), agora um uso explícito de declaração de uso.
Como você observou corretamente em sua postagem original (estou me referindo à observação "Não polimórfica"), esse comportamento pode ser visto como uma violação do relacionamento IS-A entre as classes. Isso é verdade, mas aparentemente naquela época foi decidido que, no final, esconder-se provaria ser um mal menor.
fonte
nullptr
, objetarei ao seu exemplo dizendo "se você quiser chamar avoid*
versão, use um tipo de ponteiro". Existe um exemplo diferente em que isso pode ser ruim?d->foo()
você não obtenha o "Is-aBase
", masstatic_cast<Base*>(d)->foo()
sim , incluindo o envio dinâmico.As regras de resolução de nomes dizem que a pesquisa de nomes para no primeiro escopo em que um nome correspondente é encontrado. Nesse ponto, as regras de resolução de sobrecarga entram em ação para encontrar a melhor correspondência de funções disponíveis.
Nesse caso,
gogo(int*)
é encontrado (sozinho) no escopo da classe Derived e, como não há conversão padrão de int para int *, a pesquisa falha.A solução é trazer as declarações Base por meio de uma declaração using na classe Derived:
... permitiria que as regras de pesquisa de nome encontrassem todos os candidatos e, portanto, a resolução de sobrecarga continuaria conforme o esperado.
fonte
Isso é "por design". Em C ++, a resolução de sobrecarga para esse tipo de método funciona da seguinte maneira.
Como o Derivado não possui uma função correspondente denominada "gogo", a resolução de sobrecarga falha.
fonte
Ocultar nomes faz sentido porque evita ambiguidades na resolução de nomes.
Considere este código:
Se
Base::func(float)
não estivesse ocultoDerived::func(double)
em Derived, chamaríamos a função de classe base ao chamardobj.func(0.f)
, mesmo que um float possa ser promovido para um double.Referência: http://bastian.rieck.ru/blog/posts/2016/name_hiding_cxx/
fonte