Судя по формулировке вашего вопроса (вы использовали слово «скрыть»), вы уже знаете, что здесь происходит. Это явление называется «сокрытие имени». По какой-то причине каждый раз, когда кто-то задает вопрос о том, почему происходит скрытие имени, люди, которые отвечают, либо говорят, что это называется «скрытием имени», и объясняют, как это работает (что вы, вероятно, уже знаете), либо объясняют, как это переопределить (что вы никогда не задавался вопросом), но, кажется, никому нет дела до вопроса «почему».
Решение, обоснование сокрытия имени, т. Е. Почему оно на самом деле было разработано для C ++, состоит в том, чтобы избежать определенного противоречивого, непредвиденного и потенциально опасного поведения, которое может иметь место, если унаследованный набор перегруженных функций может смешиваться с текущим набором перегрузки в данном классе. Вы, вероятно, знаете, что в C ++ разрешение перегрузки работает, выбирая лучшую функцию из набора кандидатов. Это делается путем сопоставления типов аргументов с типами параметров. Правила соответствия иногда могут быть сложными и часто приводить к результатам, которые могут быть восприняты неподготовленным пользователем как нелогичные. Добавление новых функций к набору ранее существующих может привести к довольно резкому изменению результатов разрешения перегрузки.
Например, допустим, что в базовом классе B
есть функция-член, foo
которая принимает параметр типа void *
, и все вызовы foo(NULL)
разрешаются B::foo(void *)
. Допустим, имя не скрыто, и это B::foo(void *)
видно во многих различных классах, происходящих из B
. Однако, скажем, в некотором [косвенном, удаленном] потомке D
класса B
определена функция foo(int)
. Теперь без скрытия имени D
есть foo(void *)
и foo(int)
видимый, и участвующий в разрешении перегрузки. К какой функции будут обращаться вызовы foo(NULL)
, если она выполняется через объект типа D
? Они будут разрешены D::foo(int)
, так int
как это лучшее соответствие для целого нуля (т.е.NULL
), чем любой тип указателя. Таким образом, по всей иерархии призываетfoo(NULL)
разрешается одной функции, тогда как в D
(и под) они внезапно разрешаются к другой.
Другой пример приведен в «Проектировании и развитии C ++» , стр. 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*)
}
Без этого правила состояние b будет частично обновлено, что приведет к нарезке.
Такое поведение было сочтено нежелательным, когда язык был разработан. В качестве лучшего подхода было решено следовать спецификации «скрытия имен», что означает, что каждый класс начинается с «чистого листа» относительно каждого имени метода, которое он объявляет. Чтобы переопределить это поведение, от пользователя требуется явное действие: первоначально переопределение унаследованного метода (ов) (в настоящее время не рекомендуется), теперь явное использование using-декларации.
Как вы правильно заметили в своем первоначальном посте (я имею в виду замечание «Не полиморфно»), такое поведение может рассматриваться как нарушение отношения IS-A между классами. Это правда, но, видимо, тогда было решено, что в конце концов сокрытие имени окажется меньшим злом.