Судя по формулировке вашего вопроса (вы использовали слово «скрыть»), вы уже знаете, что здесь происходит. Это явление называется «сокрытие имени». По какой-то причине каждый раз, когда кто-то задает вопрос о , почему происходит скрытие имени, люди, которые отвечают, либо говорят, что это называется "скрытием имени", и объясняют, как это работает (что вы, вероятно, уже знаете), либо объясняют, как переопределить это (о чем вы никогда не спрашивали), но, похоже, никому нет дела до ответа на вопрос «почему».
Решение, обоснование сокрытия имени, т. Е. , почему на самом деле было разработано в 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 между классами. Это правда, но, видимо, тогда было решено, что в конце концов сокрытие имени окажется меньшим злом.