Почему переопределенный функционирует в производном классе, скрывают другие перегрузки базового класса?

В дизайне API я всегда находил это представление ведущих идей очень полезным:
, Как Разработать Хороший API и Почему это Вопросы - Joshua Bloch

Вот является выборкой, я рекомендовал бы читать все это / просмотр ролика.

II. Общие принципы

  • API Должен Сделать Одну Вещь и Сделать это Хорошо
  • , API Должен Быть Как можно меньше, Но Никакое Меньшее
  • Реализация не Должна Влиять на API
  • , Минимизируют Доступность Всего
  • Имена, Matter†“API является немного Языка
  • Вопросы Документации
  • , Документ Неукоснительно
  • Рассматривает Последствия Производительности Проектных решений API
  • , Эффекты Проектных решений API на Производительности Реальны и Постоянные
  • , API Должен Сосуществовать Мирно с Платформой

III. Дизайн

  • класса Минимизирует Переменчивость
  • Подкласс Только там, где это Имеет Смысл
  • Дизайн и Документ для Наследования, или иначе Запретите его

IV. Дизайн

  • метода не Заставляет Клиент Сделать Что-либо, что Модуль Мог Сделать
  • , не Нарушают Принцип Наименьшего количества Удивления
  • Сбой Быстро - Ошибки Отчета как можно скорее После того, как Они Происходят
  • , Обеспечивают Программируемый Доступ ко Всем Доступным данным в Строковой Форме
  • Перегрузка С Осторожностью
  • Использование Соответствующие Типы Параметра и Возврата
  • Использование, Последовательное Упорядочивание Параметра Через Методы
  • Избегает, чтобы Длинные Списки параметров
  • Избежали Возвращаемых значений который Спрос Исключительная Обработка

212
задан Rob Kennedy 26 October 2009 в 19:40
поделиться

3 ответа

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

Решение, обоснование сокрытия имени, то есть , почему на самом деле было разработано на 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 отношений между классами. Это правда, но, видимо, тогда было решено, что в конце концов сокрытие имени окажется меньшим злом.

393
ответ дан 23 November 2019 в 04:30
поделиться

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.

43
ответ дан 23 November 2019 в 04:30
поделиться

Это «По замыслу». В C ++ разрешение перегрузки для этого типа метода работает следующим образом.

  • Начиная с типа ссылки и затем переходя к базовому типу, найдите первый тип, у которого есть метод с именем «gogo»
  • Рассмотрение только методов с именем "gogo" в этом типе найдите соответствующую перегрузку

Поскольку Derived не имеет функции сопоставления с именем "gogo", разрешение перегрузки не выполняется.

12
ответ дан 23 November 2019 в 04:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: