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 membro foo
que aceita um parâmetro do tipo void *
e todas as chamadas para foo(NULL)
sejam resolvidas B::foo(void *)
. Digamos que não haja ocultação de nomes e isso B::foo(void *)
é visível em várias classes diferentes, descendentes de B
. No entanto, digamos que em algum descendente [indireto, remoto] D
da classe B
uma função foo(int)
seja definida. Agora, sem nome esconderijo D
tem tanto foo(void *)
e foo(int)
visível e participando de resolução de sobrecarga. Para qual função as chamadas serão foo(NULL)
resolvidas, se feitas por meio de um objeto do tipo D
? Eles resolverão D::foo(int)
, já que int
é uma melhor correspondência para o zero integral (ou seja,NULL
) que qualquer tipo de ponteiro. Assim, em toda a hierarquia, as chamadas são foo(NULL)
resolvidas para uma função, enquanto em D
(e abaixo) elas repentinamente são resolvidas para outra.
Outro exemplo é dado em The Design and Evolution of 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*)
}
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.