Почему «виртуальный» необязателен для переопределенных методов в производных классах? - PullRequest
15 голосов
/ 03 июня 2010

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

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

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

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

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

Ответы [ 5 ]

11 голосов
/ 03 июня 2010

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

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

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

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

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

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

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

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

6 голосов
/ 03 июня 2010

Как примечание, в 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
};

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

3 голосов
/ 03 июня 2010

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

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

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

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

2 голосов
/ 03 июня 2010

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

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

0 голосов
/ 03 июня 2010

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

...