À en juger par le libellé de votre question (vous avez utilisé le mot «cacher»), vous savez déjà ce qui se passe ici. Le phénomène est appelé "dissimulation de nom". Pour une raison quelconque, chaque fois que quelqu'un pose une question sur la raison pour laquelle le masquage de nom se produit, les personnes qui répondent disent que cela s'appelle "masquage de nom" et expliquent comment cela fonctionne (que vous connaissez probablement déjà), ou expliquent comment le remplacer (ce que vous n'a jamais posé de questions), mais personne ne semble se soucier de répondre à la véritable question du «pourquoi».
La décision, la raison d'être du nom qui se cache, c'est-à-dire pourquoi il a été conçu en C ++, est d'éviter certains comportements contre-intuitifs, imprévus et potentiellement dangereux qui pourraient se produire si l'ensemble hérité de fonctions surchargées était autorisé à se mélanger avec l'ensemble actuel de surcharges dans la classe donnée. Vous savez probablement que la résolution de surcharge en C ++ fonctionne en choisissant la meilleure fonction dans l'ensemble des candidats. Cela se fait en faisant correspondre les types d'arguments aux types de paramètres. Les règles de correspondance peuvent parfois être compliquées et conduire souvent à des résultats qui peuvent être perçus comme illogiques par un utilisateur non préparé. L'ajout de nouvelles fonctions à un ensemble de fonctions existantes peut entraîner un changement assez radical des résultats de résolution de surcharge.
Par exemple, supposons que la classe de base B
possède une fonction membre foo
qui accepte un paramètre de type void *
, et tous les appels à foo(NULL)
sont résolus en B::foo(void *)
. Disons qu'il n'y a pas de nom caché et cela B::foo(void *)
est visible dans de nombreuses classes différentes qui descendent B
. Cependant, disons que dans un descendant D
de classe [indirect, distant], B
une fonction foo(int)
est définie. Désormais, sans nom, le masquage D
a à la fois foo(void *)
et foo(int)
visible et participe à la résolution de surcharge. À quelle fonction les appels seront-ils foo(NULL)
résolus s'ils sont effectués via un objet de type D
? Ils se résoudront à D::foo(int)
, puisqu'il int
s'agit d'une meilleure correspondance pour le zéro intégral (c.-à-d.NULL
) que tout type de pointeur. Ainsi, tout au long de la hiérarchie, les appels à se foo(NULL)
résoudre à une fonction, tandis que dans D
(et sous) ils se résolvent soudainement à une autre.
Un autre exemple est donné dans La conception et l'évolution de C ++ , page 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*)
}
Sans cette règle, l'état de b serait partiellement mis à jour, entraînant un découpage.
Ce comportement a été jugé indésirable lors de la conception du langage. Comme meilleure approche, il a été décidé de suivre la spécification "masquage de nom", ce qui signifie que chaque classe commence par une "feuille blanche" en ce qui concerne chaque nom de méthode qu'elle déclare. Afin de remplacer ce comportement, une action explicite est requise de la part de l'utilisateur: à l'origine une redéclaration de méthode (s) héritée (actuellement déconseillée), maintenant une utilisation explicite de using-declaration.
Comme vous l'avez correctement observé dans votre message d'origine (je fais référence à la remarque "non polymorphe"), ce comportement peut être considéré comme une violation de la relation IS-A entre les classes. C'est vrai, mais apparemment à l'époque, il a été décidé qu'à la fin, la dissimulation du nom se révélerait être un moindre mal.