Переопределение по умолчанию виртуального деструктора
Всем известно, что дескриптор базового класса обычно должен быть виртуальным. Но что такое деструктор производного класса? В С++ 11 мы имеем ключевое слово "переопределить" и возможность явно использовать деструктор по умолчанию.
struct Parent
{
std::string a;
virtual ~Parent()
{
}
};
struct Child: public Parent
{
std::string b;
~Child() override = default;
};
Правильно ли использовать оба слова "переопределить" и "= по умолчанию" в деструкторе класса Child? Будет ли компилятор генерировать правильный виртуальный деструктор в этом случае?
Если да, то можем ли мы думать, что это хороший стиль кодирования, и мы всегда должны объявлять деструкторы производных классов таким образом, чтобы гарантировать, что деструкторы базового класса являются виртуальными?
Ответы
Ответ 1
Правильно ли использовать оба слова "переопределить" и "= по умолчанию" в деструкторе класса Child? Будет ли компилятор генерировать правильный виртуальный деструктор в этом случае?
Да, это правильно. В любом разумном компиляторе, если код компилируется без ошибок, это определение деструктора будет no-op: его отсутствие не должно изменять поведение кода.
можем ли мы думать, что это хороший стиль кодирования
Это вопрос предпочтения. Для меня это имеет смысл только в том случае, если шаблон базового класса является шаблоном: тогда он будет применять требование к базовому классу, чтобы иметь виртуальный деструктор. В противном случае, когда базовый тип исправлен, я считаю, что такой код является помехой. Это не так, как если бы базовый класс волшебным образом изменился. Но, если у вас есть умные товарищи по команде, которые любят менять вещи, не проверяя код, который зависит от того, что они могут сломать, лучше оставить определение деструктора в качестве дополнительного уровня защиты.
Ответ 2
override
- не что иное, как защитная сетка. Деструктор дочернего класса всегда будет виртуальным, если деструктор базового класса является виртуальным, независимо от того, как он объявлен или вообще не объявлен (т.е. С использованием неявно объявленного).
Ответ 3
Существует (по крайней мере) одна причина для использования override
здесь - вы гарантируете, что деструктор базового класса всегда является виртуальным. Это будет ошибка компиляции, если деструктор производного класса полагает, что он что-то переопределяет, но переопределить нечего. Это также дает вам удобное место, чтобы оставить сгенерированную документацию, если вы это делаете.
С другой стороны, я могу думать о двух причинах, чтобы не делать этого:
- Это немного странно и назад для производного класса для обеспечения поведения из базового класса.
-
Если вы определяете destuctor в заголовке (или, если вы делаете его inline), вы вводите возможность для ошибок нечетной компиляции. Скажем, ваш класс выглядит следующим образом:
struct derived {
struct impl;
std::unique_ptr<derived::impl> m_impl;
~derived() override = default;
};
Вероятно, вы получите ошибку компилятора, потому что деструктор (который встроен в класс здесь) будет искать деструктор для неполного класса derived::impl
.
Это мой круглый способ сказать, что каждая строка кода может стать ответственностью, и, возможно, лучше всего просто пропустить что-то, если оно функционально ничего не делает. Если вам действительно нужно принудительно ввести виртуальный деструктор в базовый класс из родительского класса, кто-то предложил использовать static_assert
совместно с std::has_virtual_destructor
, что даст более согласованные результаты, IMHO.
Ответ 4
Хотя деструкторы не наследуются, в стандарте четко написано, что виртуальные деструкторы производных классов переопределяют деструкторы базовых классов.
Из стандарта С++ (10.3 виртуальных функций)
6 Несмотря на то, что деструкторы не наследуются, деструктор в производном class переопределяет деструктор базового класса, объявленный виртуальным; см. 12.4 и 12.5.
С другой стороны, также написано (9.2 члена класса)
8. Вирд-спецификатор должен содержать не более одного из каждого вир-специфика. Вирд-спецификатор должен появляться только в объявлении a виртуальная функция-член (10.3).
Хотя деструкторы называются как специальные функции-члены, тем не менее они также являются функциями-членами.
Я уверен, что С++ Standard должен быть отредактирован таким образом, чтобы было однозначно, может ли деструктор иметь virt-specifier override
. В настоящее время неясно.
Ответ 5
Я думаю, что "переопределить" является неправильным для деструктора.
Когда вы переопределяете виртуальную функцию, вы ее заменяете.
Деструкторы цепочки, поэтому вы не можете буквально перекрыть деструктор
Ответ 6
Ссылка CPP говорит, что override
гарантирует, что функция virtual
и что она действительно отменяет виртуальную функцию. Таким образом, ключевое слово override
гарантирует, что деструктор виртуальный.
Если вы укажете override
, но не = default
, вы получите ошибку компоновщика.
Вам ничего не нужно делать. Выход из Child
dtor undefined работает просто отлично:
#include <iostream>
struct Notify {
~Notify() { std::cout << "dtor" << std::endl; }
};
struct Parent {
std::string a;
virtual ~Parent() {}
};
struct Child : public Parent {
std::string b;
Notify n;
};
int main(int argc, char **argv) {
Parent *p = new Child();
delete p;
}
Будет выводиться dtor
. Если вы удалите virtual
в Parent::~Parent
, он ничего не выведет, потому что это поведение undefined, как указано в комментариях.
Хороший стиль - вообще не упоминать Child::~Child
. Если вы не можете доверять базовому классу, объявившему его виртуальным, то ваше предложение с override
и = default
будет работать; Я надеюсь, что есть лучшие способы гарантировать, что вместо того, чтобы засорять ваш код этими объявлениями деструктора.
Ответ 7
Хорошо для меня ключевое слово override после деструктора производного класса не имеет никакого смысла, потому что всегда происходит то, что сначала вызывается деструктор производного класса, а затем вызывается деструктор родительского класса. Переопределите заметки о том, что функция отличается от ее партер-виртуальной функции, которая в этом случае неверна, потому что они оба вызываются.
Так что я бы сказал, это то, что все зависит от предпочтения.
P.S.: Я считаю, что деструктор default
никогда не бывает плохим. Он сообщает компилятору, что вы его оставляете, чтобы он мог сделать некоторые внутренние оптимизации. Это также относится к деструктору производного класса.