Указатель базового класса, указывающий на производный класс, не может получить доступ к методу производного класса - PullRequest
0 голосов
/ 15 октября 2019

Я изучаю наследование в C ++ 11 и обнаружил, что если производный класс переопределил имя виртуальной функции, но с другим прототипом, указатель базового класса, назначенный с указателем на производный класс, может получить доступ только кверсия функции базового класса. Производная функция версии недоступна. Интересно, почему это происходит?

class Enemy {
public:
  virtual void describe() { std::cout << "Enemy"; }
};
class Dragon : public Enemy {
public:
  virtual void describe(int dummy) { std::cout << "Dragon"; }
};

В main,

Dragon foo;
Enemy* pe = &foo;
pe->describe(); // Enemy
foo.describe(1); // Dragon
pe->describe(1); // no matching function, candidate is Enemy::describe()

Из того, что я знаю о таблицах виртуальных функций, производный объект, на который указывает pe (т. Е. foo) должен иметь член vpointer, который указывает на vtable Dragon. Я также знаю, что переопределение имени функции в производном классе скроет все функции с тем же именем в базовом классе. Таким образом, в vtable Дракона адресом `description 'должна быть функция с параметром int dummy.

Но оказывается, что pe может получить доступ к Enemy версии метода, которая должнабыть скрытымИ pe не может получить доступ к версии метода Dragon, которая должна быть в vtable таблицы pe. Он работает так, как будто используется таблица Enemy. Почему это происходит?

Обновление : Я думаю, теперь я более или менее понимаю механизмы, стоящие за этим. Вот моя гипотеза:

Поскольку это указатель на Enemy, программа сначала найдет имя метода в области действия Enemy. Если имя не найдено, компилятор выдает ошибку. Если он не виртуальный, тогда позвоните. Если он виртуальный, запишите смещение метода в таблицу Enemy. Затем программа использует это смещение для доступа к нужному методу в vtable целевого объекта.

Если метод корректно переопределен, адрес функции в vtable целевого объекта с этим смещением был бы изменен. В противном случае это будет тот же адрес функции, что и в таблице Enemy, как в примере.

Поскольку Dragon describe с int dummy - это другой прототип, ондобавлен в vtable Dragon после оригинального describe, унаследованного от Enemy. Но к версии int dummy нельзя получить доступ из Enemy*, потому что vtable Enemy даже не имеет этого смещения.

Это правильно?

Ответы [ 3 ]

0 голосов
/ 15 октября 2019

В C ++ функции, которые имеют одинаковые имена, но разные параметры, являются полностью независимыми функциями, , которые не имеют ничего общего друг с другом . Тот факт, что они имеют одинаковое имя, совершенно несущественен.

Это точно так же, как если бы вы назвали функцию в базовом классе «apple» и функцию в производном классе «banana». Поскольку в базовом классе нет функции «банан», вы не можете явно вызывать ее в базовом классе. Банановая функция в производном классе, очевидно, не переопределяет функцию в базовом классе.

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

Это неверно. Он скрывает это, только если у него есть то же имя, но также идентичные параметры (и любые квалификаторы, если они есть или нет).

0 голосов
/ 15 октября 2019

Фактически у вас есть:

class Enemy {
public:
  virtual void describe() { std::cout << "Enemy"; }
};

class Dragon : public Enemy {
public:
  // void describe() override { Enemy::describe(); } // Hidden
  virtual void describe(int dummy) { std::cout << "Dragon"; }
};

Выбор метода перегрузки выполняется статически:

  • указатели / ссылки на Enemy только см. void Enemy::describe()

  • указатели / ссылки на Dragon см. Только void Dragon::describe(int) (но может явно иметь доступ к void Enemy::describe()).

Затем виртуальная отправкавыполняется с типом времени выполнения.

Так что

Dragon foo;
Enemy* pe = &foo;

foo.describe();         // KO: Enemy::describe() not visible (1)
foo.Enemy::describe();  // OK: Enemy::describe()
foo.describe(1);        // OK: Dragon::describe(int)

pe->describe();         // OK: Enemy::describe()
pe->describe(1);        // KO: No Enemy::describe(int)
pe->Dragon::describe(1);// KO: Dragon is not a base class of Enemy

(1) можно исправить, изменив Dragon на

class Dragon : public Enemy {
public:
  using Enemy::describe; // Unhide Enemy::describe()

  virtual void describe(int dummy) { std::cout << "Dragon"; }
};
0 голосов
/ 15 октября 2019

Функции с одинаковыми именами, но разными сигнатурами, по сути, являются разными функциями.

Объявляя virtual void describe(int dummy) в своем классе Dragon, вы объявили новую виртуальную функцию, не переопределяя исходную (* 1005). * в Enemy). Вы можете переопределить только виртуальные функции с одной и той же сигнатурой.

Вы не можете вызвать describe(1) для указателя на Enemy, потому что c ++ вызывает функцию в соответствии с типом времени компиляции экземпляра (хотя такой вызов может быть динамически отправлендля вызова фактического метода переопределения).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...