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

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

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

мой вопрос: каково обоснование для создания virtual необязательно?

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

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

5 ответов


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

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

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

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


как связанная заметка, в C++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
};

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


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

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

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

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


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

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


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