Подтвердить что ты не робот

Почему "virtual" необязателен для переопределенных методов в производных классах?

Когда метод объявляется как virtual в классе, его переопределения в производных классах автоматически рассматриваются как virtual, а язык С++ делает это ключевое слово virtual необязательным в этом случае:

class Base {
    virtual void f();
};
class Derived : public Base {
    void f(); // 'virtual' is optional but implied.
};

Мой вопрос: что является основанием для того, чтобы сделать virtual необязательным?

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

Например, иногда, когда я читаю код других, мне интересно, является ли метод виртуальным, и я должен отслеживать его суперклассы, чтобы определить это. И некоторые стандарты кодирования (Google) делают его обязательным для размещения ключевого слова virtual во всех подклассах.

4b9b3361

Ответ 1

Да, в этом случае было бы лучше сделать компилятор для принудительного использования виртуального, и я согласен с тем, что это ошибка в дизайне, которая поддерживается для обратной совместимости.

Однако есть один трюк, который был бы без него невозможным:

class NonVirtualBase {
  void func() {};
};

class VirtualBase {
  virtual void func() = 0;
};

template<typename VirtualChoice>
class CompileTimeVirtualityChoice : public VirtualChoice {
  void func() {}
};

С вышесказанным мы собираем время выбора, мы хотим виртуальности func или нет:

CompileTimeVirtualityChoice<VirtualBase> -- func is virtual
CompileTimeVirtualityChoice<NonVirtualBase> -- func is not virtual

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

Ответ 2

Как связанная заметка, в С++ 0x у вас есть возможность принудительного применения явного с вашими переопределениями с помощью нового синтаксиса атрибутов.

struct Base {
  virtual void Virtual();
  void NonVirtual();
};

struct Derived [[base_check]] : Base {
  //void Virtual(); //Error; didn't specify that you were overriding
  void Virtual [[override]](); //Not an error
  //void NonVirtual [[override]](); //Error; not virtual in Base
  //virtual void SomeRandomFunction [[override]](); //Error, doesn't exist in Base
};

Вы также можете указать, когда вы намерены скрыть элемент с помощью атрибута [[hiding]]. Это делает ваш код несколько более подробным, но во время компиляции он может поймать множество раздражающих ошибок, например, если вы сделали void Vritual() вместо void Virtual() и в итоге ввели новую функцию, когда вы хотели переопределить существующую.

Ответ 3

Слабая точка в дизайне, я согласен. Я также думаю, что было бы хорошо действительно, если бы существовал другой синтаксис для двух разных вещей:

  • Объявление виртуальной функции. То есть функция, которая может быть переопределена в производном классе. (Эта вещь фактически добавляет новую запись функции в таблицу vtable.)
  • Переопределение виртуальной функции в производном классе.

С текущими правилами С++ при переопределении функции - легко вставлять вещи. Если вы ошибаетесь в имени функции (или делаете ошибку в списке параметров) - тогда вы фактически выполняете (1) вместо (2).

И у вас нет ошибки/предупреждения. Просто получите сюрприз во время выполнения.

Ответ 4

Поскольку язык не может обеспечить "хороший" стиль, С++ обычно даже не пытается. По крайней мере, IMO, он может поставить вопрос о том, является ли включение избыточных спецификаторов подобным образом в любом случае хорошим стилем (лично я ненавижу, когда они там).

(По крайней мере, часть) стандарты кодирования Google могут иметь смысл при некоторых обстоятельствах, но, насколько это вообще касается С++, в лучшем случае они обычно считаются посредственными советами. В какой-то степени они даже признают, что некоторые из них открыто заявляют, что они действительно подходят только для их старого кода. Другие части, которые они не допускают напрямую, и (если быть полностью честными), что аргумент не будет поддерживать некоторые из их стандартов в любом случае (т.е. Некоторые из них, похоже, не имеют реального оправдания).

Ответ 5

Это хороший вопрос, и я, конечно, согласен с тем, что хороший стиль для переопределения метода virtual в производном классе, если он был объявлен виртуальным в базовом классе. Хотя существуют некоторые языки, которые создают стиль в языке (например, Google Go и, в некоторой степени, Python), С++ не является одним из этих языков. Хотя, конечно, компилятор может обнаружить, что производный класс не повторно использует ключевое слово "virtual" для чего-то объявленного "виртуального" в базовом классе (или, что более важно, что производный класс объявляет функцию с тем же именем, что и базовый класс, и он не был объявлен виртуальным в базовом классе), есть на самом деле настройки многих компиляторов для выдачи предупреждений (и даже сообщений об ошибках) в случае, если это произойдет. На этом этапе, однако, было бы нецелесообразно вводить такое требование в языке, поскольку существует слишком много кода, который не является строгим. Более того, разработчики всегда могут выбирать более строгие, чем язык, и могут вызывать уровни предупреждений компилятора.