Безпечно перекрийте віртуальними функціями 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 і додаєте переопределення в кінці реалізації функції, g ++ видає помилку; якщо ви забезпечите реалізацію вбудованої функції в декларації класу за ключовим словом override, все добре. Чому?
h9uest

3
@ h9uest overrideмає бути використаний у визначенні. Вбудована реалізація - це і визначення, і реалізація, так що добре.
Гюнтер П'єз

@hirschhornsalz Так, я отримав ту ж помилку msg від g ++. Хоча бічна примітка: і ви, і g ++ помилка msg використовували термін "визначення класу" - чи не слід використовувати "декларацію" ({заява, визначення} пара)? Ви дали зрозуміти саме в цьому конкретному контексті, сказавши "визначення та реалізація", але мені просто цікаво, чому спільнота c ++ раптом вирішує змінити терміни на заняттях?
h9uest

20

Щось на зразок overrideключового слова C # не входить до C ++.

У gcc -Woverloaded-virtualзастерігає від приховування віртуальної функції базового класу з однойменною функцією, але досить іншою підписом, щоб вона не перекривала її. Однак це не захистить вас від неможливості зміни функції через неправильне написання назви функції.


2
Це якщо ви випадково використовуєте Visual C ++
Steve Rowe

4
Використання Visual C ++ не робить overrideключового слова в C ++; це може означати, що ви використовуєте щось, що може компілювати недійсний вихідний код C ++. ;)
CB Bailey

3
Те, що переопределення недійсне на C ++, означає, що стандарт неправильний, а не Visual C ++
Jon

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

5
Як не дивно, через пару років після коментаря за допомогою VC ++ overrideключове слово не створює, здається, це було . Ну, не належне ключове слово, а спеціальний ідентифікатор у C ++ 11. Microsoft наполегливо натиснула, щоб зробити це окремим випадком і дотримуватися загального формату атрибутів, і overrideперетворила його на стандарт :)
Девід Родрігес - dribeas

18

Наскільки я знаю, ви не можете просто зробити це абстрактним?

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

Я думав, що читав на www.parashift.com, що ви можете реально реалізувати абстрактний метод. Що для мене особисто має сенс, єдине, що він робить - це змусити підкласи його реалізовувати, ніхто про це нічого не сказав, не дозволяючи мати саму реалізацію.


Тільки тепер я зрозумів, що це працює на більш ніж просто деструкторах! Чудова знахідка.
страгер

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

3
Я згоден з Майклом Берром. Зведення абстрактного базового класу не є частиною питання. Цілком розумно мати базовий клас з функціональністю у віртуальному методі, який потрібно переосмислити похідним класом. І це так само розумно хочеться захистити від іншого програміста, перейменувавши функцію в базовий клас, і змусити ваш похідний клас більше не змінювати його. Розширення Microsoft "переосмислити" в цьому випадку неоціненне. Я хотів би, щоб це було додано до стандарту, тому що, на жаль, не обійтися без цього.
Брайан

Це також запобігає виклику базового методу (скажімо BaseClass::method()) у похідній реалізації (скажімо DerivedClass::method()), наприклад, для значення за замовчуванням.
Narcolessico

11

У MSVC можна використовувати CLR override ключове слово навіть якщо ви не компілюєте для 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 () все ще може робити "нудний код за замовчуванням", але замість того, щоб бути віртуальним, у точці, коли ви хочете, щоб це робив "новий захоплюючий код", виклик базового класу абстрактним методом (тобто повинен бути замінений) які будуть поставлені вашим класом нащадків.

EDIT: І якщо пізніше ви вирішите, що деяким з ваших класів нащадків не потрібно надавати "новий захоплюючий код", ви можете змінити конспект на віртуальний і поставити порожній базовий клас реалізації цього "вставленого" функціоналу.


2

У вашому компіляторі може бути попередження, що воно може генеруватися, якщо функція базового класу стане прихованою. Якщо так, увімкніть це. Це спричинить конфлікти const та відмінності у списках параметрів. На жаль, це не виявить орфографічну помилку.

Наприклад, це попередження C4263 в Microsoft Visual C ++.


1

C ++ 11 overrideКлючове слово коли використовується з декларацією функції всередині похідного класу, примушує компілятор перевірити, що заявлена ​​функція насправді перекриває якусь функцію базового класу. В іншому випадку компілятор видасть помилку.

Отже, ви можете використовувати 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.