В дизайне API я всегда находил это представление ведущих идей очень полезным:
, Как Разработать Хороший API и Почему это Вопросы - Joshua Bloch
Вот является выборкой, я рекомендовал бы читать все это / просмотр ролика.
II. Общие принципы
- API Должен Сделать Одну Вещь и Сделать это Хорошо
- , API Должен Быть Как можно меньше, Но Никакое Меньшее
- Реализация не Должна Влиять на API
- , Минимизируют Доступность Всего
- Имена, Matter†“API является немного Языка
- Вопросы Документации
- , Документ Неукоснительно
- Рассматривает Последствия Производительности Проектных решений API
- , Эффекты Проектных решений API на Производительности Реальны и Постоянные
- , API Должен Сосуществовать Мирно с Платформой
III. Дизайн
- класса Минимизирует Переменчивость
- Подкласс Только там, где это Имеет Смысл
- Дизайн и Документ для Наследования, или иначе Запретите его
IV. Дизайн
- метода не Заставляет Клиент Сделать Что-либо, что Модуль Мог Сделать
- , не Нарушают Принцип Наименьшего количества Удивления
- Сбой Быстро - Ошибки Отчета как можно скорее После того, как Они Происходят
- , Обеспечивают Программируемый Доступ ко Всем Доступным данным в Строковой Форме
- Перегрузка С Осторожностью
- Использование Соответствующие Типы Параметра и Возврата
- Использование, Последовательное Упорядочивание Параметра Через Методы
- Избегает, чтобы Длинные Списки параметров
- Избежали Возвращаемых значений который Спрос Исключительная Обработка
Судя по формулировке вашего вопроса (вы использовали слово «скрыть»), вы уже знаете, что здесь происходит. Это явление называется «сокрытие имени». По какой-то причине каждый раз, когда кто-то задает вопрос о , почему происходит сокрытие имени , люди, которые отвечают, либо говорят, что это называется «скрытие имени», и объясняют, как это работает (что вы, вероятно, уже знаете), либо объясняют, как чтобы переопределить его (о чем вы никогда не спрашивали), но, похоже, никто не заботится о фактическом "почему" вопрос.
Решение, обоснование сокрытия имени, то есть , почему на самом деле было разработано на C ++, состоит в том, чтобы избежать определенного противоречивого, непредвиденного и потенциально опасного поведения, которое может иметь место, если унаследованный набор перегруженным функциям было разрешено смешиваться с текущим набором перегрузок в данном классе. Вы, наверное, знаете, что в C ++ разрешение перегрузки работает, выбирая лучшую функцию из набора кандидатов. Это делается путем сопоставления типов аргументов с типами параметров. Иногда правила сопоставления могут быть сложными и часто приводить к результатам, которые неподготовленный пользователь может воспринять как нелогичные. Добавление новых функций к набору ранее существовавших может привести к довольно резкому сдвигу в результатах разрешения перегрузки.
Например, пусть ' s говорят, что базовый класс 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 было бы частично обновлено, что привело бы к разрезанию.
Такое поведение было сочтено нежелательным. когда язык был разработан. В качестве лучшего подхода было решено следовать спецификации «сокрытия имени», означающей, что каждый класс начинается с «чистого листа» по отношению к каждому объявленному им имени метода. Чтобы переопределить это поведение, от пользователя требуется явное действие: первоначально повторное объявление унаследованных методов (в настоящее время не рекомендуется), теперь явное использование объявления-использования.
Как вы правильно заметили в своем исходном сообщении (Я имею в виду замечание «Не полиморфно»), такое поведение можно рассматривать как нарушение IS-A отношений между классами. Это правда, но, видимо, тогда было решено, что в конце концов сокрытие имени окажется меньшим злом.
такое поведение можно рассматривать как нарушение IS-A отношений между классами. Это правда, но, видимо, тогда было решено, что в конце концов сокрытие имени окажется меньшим злом. такое поведение можно рассматривать как нарушение IS-A отношений между классами. Это правда, но, видимо, тогда было решено, что в конце концов сокрытие имени окажется меньшим злом.The name resolution rules say that name lookup stops in the first scope in which a matching name is found. At that point, the overload resolution rules kick in to find the best match of available functions.
In this case, gogo(int*)
is found (alone) in the Derived class scope, and as there's no standard conversion from int to int*, the lookup fails.
The solution is to bring the Base declarations in via a using declaration in the Derived class:
using Base::gogo;
...would allow the name lookup rules to find all candidates and thus the overload resolution would proceed as you expected.
Это «По замыслу». В C ++ разрешение перегрузки для этого типа метода работает следующим образом.
Поскольку Derived не имеет функции сопоставления с именем "gogo", разрешение перегрузки не выполняется.