Почему "virtual" необязателен для переопределенных методов в производных классах?
Когда метод объявляется как virtual
в классе, его переопределения в производных классах автоматически рассматриваются как virtual
, а язык С++ делает это ключевое слово virtual
необязательным в этом случае:
class Base {
virtual void f();
};
class Derived : public Base {
void f(); // 'virtual' is optional but implied.
};
Мой вопрос: что является основанием для того, чтобы сделать virtual
необязательным?
Я знаю, что компилятору не обязательно говорить об этом, но я думаю, что разработчики выиграют, если бы такое ограничение было принудительно применено компилятором.
Например, иногда, когда я читаю код других, мне интересно, является ли метод виртуальным, и я должен отслеживать его суперклассы, чтобы определить это. И некоторые стандарты кодирования (Google) делают его обязательным для размещения ключевого слова virtual
во всех подклассах.
Ответы
Ответ 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" для чего-то объявленного "виртуального" в базовом классе (или, что более важно, что производный класс объявляет функцию с тем же именем, что и базовый класс, и он не был объявлен виртуальным в базовом классе), есть на самом деле настройки многих компиляторов для выдачи предупреждений (и даже сообщений об ошибках) в случае, если это произойдет. На этом этапе, однако, было бы нецелесообразно вводить такое требование в языке, поскольку существует слишком много кода, который не является строгим. Более того, разработчики всегда могут выбирать более строгие, чем язык, и могут вызывать уровни предупреждений компилятора.