Безопасное переопределение виртуальных функций C ++


100

У меня есть базовый класс с виртуальной функцией, и я хочу переопределить эту функцию в производном классе. Есть ли способ заставить компилятор проверить, действительно ли функция, объявленная в производном классе, переопределяет функцию в базовом классе? Я хотел бы добавить какой-нибудь макрос или что-то, что гарантирует, что я случайно не объявил новую функцию, вместо того, чтобы переопределить старую.

Вот пример:

class parent {
public:
  virtual void handle_event(int something) const {
    // boring default code
  }
};

class child : public parent {
public:
  virtual void handle_event(int something) {
    // new exciting code
  }
};

int main() {
  parent *p = new child();
  p->handle_event(1);
}

Здесь parent::handle_event()вызывается вместо child::handle_event(), потому что дочерний метод пропускает constобъявление и поэтому объявляет новый метод. Это также может быть опечатка в имени функции или незначительное различие в типах параметров. Это также может легко произойти, если интерфейс базового класса изменится и где-то не был обновлен какой-либо производный класс, чтобы отразить это изменение.

Есть ли способ избежать этой проблемы, могу ли я каким-то образом сказать компилятору или другому инструменту проверить это за меня? Любые полезные флаги компилятора (желательно для g ++)? Как избежать этих проблем?


2
Отличный вопрос, я пытаюсь понять, почему моя функция дочернего класса не вызывается уже час!
Акаш Манкар

Ответы:


89

Начиная с g ++ 4.7, он понимает новое overrideключевое слово C ++ 11 :

class child : public parent {
    public:
      // force handle_event to override a existing function in parent
      // error out if the function with the correct signature does not exist
      void handle_event(int something) override;
};

@hirschhornsalz: Я обнаружил, что когда вы реализуете функцию handle_event и добавляете override в конце реализации функции, g ++ выдает ошибку; если вы предоставите встроенную реализацию функции в объявлении класса после ключевого слова override, все в порядке. Зачем?
h9uest 04

3
В определении overrideнеобходимо использовать @ h9uest . Встроенная реализация - это и определение, и реализация, так что это нормально.
Gunther Piez 05

@hirschhornsalz да, я получил то же сообщение об ошибке от g ++. Однако примечание: и вы, и сообщение об ошибке g ++ использовали термин «определение класса» - разве мы не должны использовать «объявление» (пара {объявление, определение})? Вы четко заявили о себе в этом конкретном контексте, сказав «определение и реализация», но мне просто интересно, почему сообщество C ++ внезапно решает изменить термины для классов?
h9uest 05

20

Что-то вроде overrideключевого слова C # не является частью C ++.

В gcc -Woverloaded-virtualпредостерегает от сокрытия виртуальной функции базового класса функцией с тем же именем, но с достаточно другой подписью, чтобы она не переопределяла ее. Однако это не защитит вас от невозможности переопределить функцию из-за неправильного написания самого имени функции.


2
Это если вы используете Visual C ++
Стив Роу,

4
Использование Visual C ++ не overrideсоздает ключевого слова в C ++; это может означать, что вы используете что-то, что может скомпилировать некорректный исходный код C ++. ;)
CB Bailey

3
Тот факт, что переопределение недопустимо для C ++, означает, что стандарт неверен, а не Visual C ++,
Джон

2
@Jon: Хорошо, теперь я вижу, к чему вы клонили. Лично я мог взять или оставить overrideфункциональность стиля C # ; У меня редко возникали проблемы с неудачными переопределениями, и их было относительно легко диагностировать и исправить. Думаю, я не согласен с тем, что пользователи VC ++ должны его использовать. Я бы предпочел, чтобы C ++ выглядел как C ++ на всех платформах, даже если один конкретный проект не нужно переносить. Стоит отметить , что C ++ 0x будет иметь [[base_check]], [[override]]и [[hiding]]атрибуты , так что вы можете выбрать , чтобы отменить проверку , если это необходимо.
CB Bailey

5
Как ни странно, через пару лет после того, как комментарий с использованием VC ++ не превратился overrideв ключевое слово, похоже, он сделал . Ну, не правильное ключевое слово, а специальный идентификатор в C ++ 11. Microsoft override
приложила достаточно усилий

18

Насколько я знаю, нельзя просто сделать это абстрактным?

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

Я думал, что читал на www.parashift.com, что вы действительно можете реализовать абстрактный метод. Что имеет смысл лично для меня, единственное, что он делает, это заставляет подклассы реализовывать его, никто ничего не сказал о том, что ему не разрешено иметь саму реализацию.


Только сейчас я понял, что это работает не только с деструкторами! Отличная находка.
strager

1
У этого есть пара потенциальных недостатков: 1) еще одна вещь, которая помечает один или несколько методов как абстрактные, - это то, что базовый класс не создается экземпляром, что может быть проблемой, если это не является частью предполагаемого использования класса. 2) базовый класс может быть не вашим, чтобы изменять в первую очередь.
Майкл Берр,

3
Я согласен с Майклом Бёрром. Создание абстрактного базового класса не является частью вопроса. Совершенно разумно иметь базовый класс с функциональностью в виртуальном методе, который вы хотите, чтобы производный класс переопределил. И так же разумно хотеть защитить себя от того, что другой программист переименует функцию в базовом классе и заставит ваш производный класс больше не переопределять ее. Расширение Microsoft «override» в этом случае неоценимо. Я бы хотел, чтобы он был добавлен в стандарт, потому что, к сожалению, без него не обойтись.
Брайан

Это также предотвращает BaseClass::method()вызов базового метода (скажем ) в производной реализации (скажем DerivedClass::method()), например, для значения по умолчанию.
Narcolessico 05

11

В MSVC вы можете использовать overrideключевое слово CLR, даже если вы не компилируете для CLR.

В g ++ нет прямого способа обеспечить это во всех случаях; другие люди дали хорошие ответы о том, как улавливать различия сигнатур с помощью -Woverloaded-virtual. В будущей версии кто-то может добавить подобный __attribute__ ((override))или эквивалентный синтаксис с использованием синтаксиса C ++ 0x.


9

В MSVC ++ вы можете использовать ключевое словоoverride

class child : public parent {
public:
  virtual void handle_event(int something) <b>override</b> {
    // new exciting code
  }
};

override работает как с машинным кодом, так и с кодом CLR в MSVC ++.


5

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

@Ray Ваш код недействителен.

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

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

class parent {
public:
  virtual void handle_event(int something) const = 0;
};

void parent::handle_event( int something ) { /* do w/e you want here. */ }

3

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

handle_event () может по-прежнему выполнять «скучный код по умолчанию», но вместо того, чтобы быть виртуальным, в точке, где вы хотите, чтобы он выполнял «новый захватывающий код», базовый класс должен вызывать абстрактный метод (т.е. метод, который должен быть переопределен) который будет предоставлен вашим потомком.

РЕДАКТИРОВАТЬ: И если вы позже решите, что некоторые из ваших потомков классов не должны предоставлять «новый захватывающий код», вы можете изменить абстрактное на виртуальное и предоставить пустую реализацию базового класса этой «вставленной» функциональности.


2

У вашего компилятора может быть предупреждение, которое он может сгенерировать, если функция базового класса станет скрытой. Если это так, включите его. Это отловит константные конфликты и различия в списках параметров. К сожалению, это не обнаружит орфографическую ошибку.

Например, это предупреждение C4263 в Microsoft Visual C ++.


1

overrideКлючевое слово C ++ 11 при использовании с объявлением функции внутри производного класса заставляет компилятор проверять, действительно ли объявленная функция заменяет некоторую функцию базового класса. В противном случае компилятор выдаст ошибку.

Следовательно, вы можете использовать overrideспецификатор для обеспечения динамического полиморфизма (переопределения функции).

class derived: public base{
public:
  virtual void func_name(int var_name) override {
    // statement
  }
};
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.