Чи успадковуються віртуальні деструктори?


77

Якщо у мене є базовий клас з віртуальним деструктором. Чи має похідний клас для оголошення віртуального деструктора?

class base {
public:
    virtual ~base () {}
};

class derived : base {
public:
    virtual ~derived () {} // 1)
    ~derived () {}  // 2)
};

Конкретні питання:

  1. Чи однакові 1) та 2)? Чи є 2) автоматично віртуальним через його основу чи він "зупиняє" віртуальність?
  2. Чи можна опустити похідний деструктор, якщо він не має нічого спільного?
  3. Яка найкраща практика для оголошення похідного деструктора? Заявити його віртуальним, невіртуальним або опустити, якщо це можливо?

Відповіді:


92
  1. Так, вони однакові. Похідний клас, який не оголошує щось віртуальним, не заважає йому бути віртуальним. Насправді жоден спосіб (включаючи деструктор) не може бути віртуальним у похідному класі, якщо він був віртуальним у базовому класі. У> = C ++ 11 ви можете використовувати, finalщоб запобігти його перевизначенню у похідних класах, але це не заважає йому бути віртуальним.
  2. Так, деструктор у похідному класі можна опустити, якщо він не має нічого спільного. І неважливо, віртуальний він чи ні.
  3. Я б опустив це, якщо це можливо. І я завжди використовую virtualключове слово знову для віртуальних функцій у похідних класах з міркувань ясності. Люди не повинні пройти весь шлях до ієрархії успадкування, щоб з’ясувати, що функція є віртуальною. Крім того, якщо ваш клас можна скопіювати або перенести без необхідності декларувати власну конструкцію копії чи переміщення, оголошення будь-якого деструктора (навіть якщо ви його визначите як default) змусить вас оголосити конструктори копіювання та переміщення та оператори присвоєння, якщо ви хочете їх, оскільки компілятор більше не буде вкладати їх вам.

Як невеликий момент для пункту 3. У коментарях зазначалося, що якщо деклактор не оголошений, компілятор генерує типовий (який все ще є віртуальним). І це за замовчуванням - це вбудована функція.

Вбудовані функції потенційно піддають більшу частину вашої програми змінам в інших частинах вашої програми та роблять двійкову сумісність спільних бібліотек складною. Крім того, посилене зчеплення може призвести до значної кількості перекомпіляцій перед певними видами змін. Наприклад, якщо ви вирішите, що дійсно хочете реалізацію для вашого віртуального деструктора, тоді кожен фрагмент коду, який його викликав, потрібно буде перекомпілювати. Тоді як якщо ви оголосили це в тілі класу, а потім визначили його порожнім у .cppфайлі, ви б добре змінили його без перекомпіляції.

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


1
Я не погоджуюсь із "опущеною" частиною. Не потрібно багато оголошувати це в заголовку та визначати (порожнє тіло) у джерелі. Якщо ви це зробите, ви завжди можете повернутися і додати кілька кроків (реєстрація?), Не примушуючи своїх клієнтів перекомпілювати.
Matthieu M.

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

3
Я щойно дізнався з цього виступу, що оголошення віртуального деструктора насправді призведе до того, що ваш клас стане незмінним! Тому щоразу, коли ви оголошуєте віртуальний деструктор, ви також повинні надати ціле правило 5, якщо ви хочете ці властивості. Ще більше підстав опускати, коли це можливо.
Ніл Трафт,

1
"Крім того, якщо ваш клас можна скопіювати або перенести без необхідності декларувати власну конструкцію копії чи переміщення, оголошення будь-якого деструктора (навіть якщо ви визначите його за замовчуванням) змусить вас оголосити конструктори копіювання та переміщення та оператори присвоєння, якщо ви хочете, щоб їх компілятор більше не додавав вам. " Це неправильно! en.cppreference.com/w/cpp/language/copy_constructor
Kaiserludi

1
@Kaiserludi - Я ще раз переконаюсь, що це правда, і виправлю свою відповідь.
Всезначний

2
  1. Деструктор автоматично є віртуальним, як і у всіх методів. Ви не можете зупинити метод від віртуальності в C ++ (якщо він вже оголошений віртуальним, тобто, тобто в Java немає еквівалента 'final')
  2. Так, це можна пропустити.
  3. Я б оголосив віртуальний деструктор, якщо маю намір цей клас підкласифікувати, незалежно від того, підкласується він іншому класу чи ні, я також вважаю за краще продовжувати оголошувати методи віртуальними, хоча це і не потрібно. Це продовжить роботу підкласів, якщо ви коли-небудь вирішите вилучити спадщину. Але я вважаю, що це лише питання стилю.

Деструктори не є автоматично віртуальними, як і будь-які інші функції-члени.

1
@ Ніл; Звичайно , немає, я мав в виду в деструкції в прикладі (тобто там , де базовий клас має віртуальну), що не деструкторов в цілому. І це справедливо для всіх методів, а не лише для деструкторів.
falstro

1
З C ++ 11 ми маємо final.
whoan

1

Функція віртуального члена зробить неявним будь-яке перевантаження цієї функції віртуальною.

Отже, віртуальний в 1) є "необов’язковим", деструктор базового класу, будучи віртуальним, робить усі вітчизняні деструктори також віртуальними.


0

1 / Так 2 / Так, це буде сформовано компілятором 3 / Вибір між тим, оголосити його віртуальним чи ні, повинен відповідати вашим правилам щодо перевизначених віртуальних членів - IMHO, є хороші аргументи в обох напрямках, просто виберіть один і дотримуйтесь його.

Я б пропустив це, якщо це можливо, але є одна річ, яка може спонукати вас заявити про це: якщо ви використовуєте згенерований компілятор, він неявно вбудований. Буває час, коли ви хочете уникати вбудованих членів (наприклад, динамічні бібліотеки).


0

Віртуальні функції неявно замінюються. Коли метод дочірнього класу відповідає підпису методу віртуальної функції з базового класу, він замінюється. Це легко сплутати та, можливо, зламати під час рефакторингу, тому існують overrideі finalключові слова, починаючи з C ++ 11, щоб явно позначити цю поведінку. Існують відповідні попередження, які забороняють мовчазну поведінку, наприклад -Wsuggest-overrideу GCC.

Існує пов’язане запитання щодо ключових слів overrideі finalключових слів щодо SO: Чи є ключове слово “override” лише перевіркою перевизначеного віртуального методу? .

І документація у посиланні на cpp https://en.cppreference.com/w/cpp/language/override

Чи варто використовувати overrideключове слово з деструкторами, все ще залишається дебатом. Наприклад, див. Обговорення у цьому зв’язаному запитанні SO: Заміна за замовчуванням віртуального деструктора . Проблема полягає в тому, що семантика віртуального деструктора відрізняється від звичайних функцій. Деструктори пов'язані ланцюгами, тому всі базові класи деструкторів називаються після дочірнього. Однак у випадку звичайного методу базові реалізації перевизначеного методу не викликаються за замовчуванням. За необхідності їх можна викликати вручну.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.