Вызов переопределенной (производного класса) версии невиртуальной функции базового класса из базового класса?

#c #overriding #virtual #vtable

#c #переопределение #виртуальный #vtable

Вопрос:

Итак, если у меня есть

 class base
{
  public:
  virtual void start();
  virtual void stop();

  void doSomething() { start(); .... stop(); }
}

class derived : public base
{
  public:
   void start();
   void stop();
}
 

вызов derived.doSomething() вызовет derived::start() и derived::stop() .
НО это работает только в том случае, если они виртуальные.

Я хотел знать, почему это не работает без virtual ключевого слова, что означает детали более низкого уровня. Я не могу много найти об этом в Интернете…

Спасибо!

Комментарии:

1. Я могу порекомендовать cppreference.com статья о виртуальных функциях.

2. Это не работает без virtual because virtual , как вы сообщаете базовому классу, что функция может быть переопределена в производном классе, и, таким образом, выяснить во время выполнения, какую версию вызывать. Таким образом, вопрос действительно является тавтологией. Это не работает без того, что заставляет его работать! Я уверен, что в Интернете должно быть много ресурсов об этом.

Ответ №1:

Без ключевого слова virtual код base::doSomething НЕ имеет ПРЕДСТАВЛЕНИЯ о производных версиях этих методов (подумайте о разрешении компоновщика)

Комментарии:

1. Это в значительной степени размахивает руками. Даже при virtual doSomething этом код не имеет представления о производных версиях этих функций. virtual сообщает компилятору, что производные классы могут переопределять эти функции, и в ответ компилятор генерирует код для поддержки переопределения. Но если производный класс не переопределяет эти функции, код остается действительным. ( virtual void start() = 0; было бы по-другому, но это не упоминается в вопросе.

2. @PeteBecker Ключевое слово virtual сообщает компоновщику разрешить вызов через виртуальную таблицу, которая должна быть разрешена во время выполнения. Не будучи виртуальной, компоновщик преобразует ее в статический вызов. Это то, что я имел в виду, говоря «БЕЗ ПОНЯТИЯ» (статически связанный или виртуальный вызов таблицы, который подразумевает НЕКОТОРОЕ ПРЕДСТАВЛЕНИЕ о возможной производной реализации).

3. Опять же: код, сгенерированный для базового класса, ничего не знает ни о каких производных классах. Он предоставляет механизм, который производные классы могут использовать для переопределения виртуальных функций базового класса.

Ответ №2:

Если функция объявлена виртуальной, все переопределения этой функции хранятся в таблице виртуальных функций-членов (vtable). Когда такая функция вызывается в вашем коде, программа на самом деле просто указывает на расположение таблицы. Затем таблица используется для определения правильной функции для вызова во время выполнения.

Если функция не объявлена виртуальной, start() и stop() в реализации базового класса doSomething всегда просто указывают на определение базового класса этих функций.

C позволяет выбирать между обоими механизмами, поскольку поиск во время выполнения в vtable приводит к некоторым накладным расходам, которые C обычно не навязывает пользователю — «вы платите только за то, что используете».

Разделы примеров и вызова в вики-статье довольно хорошо показывают техническую сторону.