Все мы знаем в каких случаях и почему деструктор базового класса должен быть виртуальным. Но вот про деструктор потомка обычно ничего конкретного не говорится. Однако в C++11 появилось ключевое слово override и возможность явно указать использование деволтного деструктора.
Насколько корректно с точки зрения стандарта использовать одновременно "override" и "=default", будет ли диструктор сгенерирован правильно в этом случае. И, если это корректно, можно ли считать, что именно так и желательно писать всегда, чтобы гарантировать что деструктор родителя виртуальный.
Здравствуйте, ksandro, Вы писали:
K>Все мы знаем в каких случаях и почему деструктор базового класса должен быть виртуальным. Но вот про деструктор потомка обычно ничего конкретного не говорится. Однако в C++11 появилось ключевое слово override и возможность явно указать использование деволтного деструктора.
K>
K>Насколько корректно с точки зрения стандарта использовать одновременно "override" и "=default", будет ли диструктор сгенерирован правильно в этом случае.
Мне представляется, что да.
Но такая запись по идее может приводить к генерации деструктора, единственной заботой которого будет вызов деструктора базового класса.
K>И, если это корректно, можно ли считать, что именно так и желательно писать всегда, чтобы гарантировать что деструктор родителя виртуальный.
ksandro:
K>Насколько корректно с точки зрения стандарта использовать одновременно "override" и "=default"
Я не видел, чтоб такое где-то запрещалось.
K>будет ли диструктор сгенерирован правильно в этом случае.
Он будет такой же, как если бы его определили так:
~Child() override {}
(раз деструктор виртуальный, значит, тривиальным ему по-любому не быть).
K>И, если это корректно, можно ли считать, что именно так и желательно писать всегда, чтобы гарантировать что деструктор родителя виртуальный.
Зачем производному классу нужна эта гарантия? Правильное удаление — это забота того, кто владеет объектом. Владелец может использовать проверку виртуального деструктора, если ему так спокойнее. Например, можно сделать аналог std::unique_ptr<T>, который будет требовать std::has_virtual_destructor<T>::value == true при любой попытке подсунуть ему в конструктор указатель на производный от T тип:
struct B {};
struct D1 : B
{
virtual ~D1() {}
};
struct D2 : D1 {};
unique_ptr_safe<B> ub(new B); // OK: the same object type
unique_ptr_safe<B> ubm(make_unique_safe<B>()); // OK: the same object type
unique_ptr_safe<B> ubd1(new D1); // error: B has no virtual dtor
unique_ptr_safe<B> ubmd1 = make_unique_safe<D1>(); // error: B has no virtual dtor
unique_ptr_safe<D1> ud1d2(new D2); // OK: D1 has virtual dtor
unique_ptr_safe<D1> ud1md2 = make_unique_safe<D2>(); // OK: D1 has virtual dtor
Пока кто-то не начнёт передавать владение через "небезопасные" указатели (или использовать плохие сторонние deleter-ы), unique_ptr_safe будет удалять только то, что умеет, а на просьбы получить во владение объект, который он удалять не умеет, будет отвечать ошибкой компиляции.
С реализацией через косвенный вызов правильного delete можно вообще не обращать внимание на наличие виртуального деструктора:
unique_ptr_universal<B> ubd1(new D1); // OK: invokes 'delete static_cast<D1 *>(get_address())' in dtor
unique_ptr_universal<B> ubmd1 = make_unique_universal<D1>(); // OK: invokes 'delete static_cast<D1 *>(get_address())' in dtor
std::shared_ptr<B> sbd1(new D1); // OK: makes a deleter for D1
std::shared_ptr<B> sbud1(make_unique_safe<D1>()); // OK: makes a deleter for D1
std::shared_ptr<B> sbmd1 = make_shared_safe<D1>(); // OK: makes a deleter for D1
Лениво сейчас расписывать реализацию unique_ptr_safe и unique_ptr_universal — там, в общем-то, и так всё очевидно: надо использовать шаблонные конструкторы и в них в первом случае проверять std::has_virtual_destructor<T>::value, а в другом случае сохранять указатель на соответствующую специализацию шаблонной функции, которая потом позовёт нужный deleter (понадобится хранить два адреса: отдельно для базового подобъекта и отдельно для целого объекта).
Здравствуйте, ksandro, Вы писали:
K>Насколько корректно с точки зрения стандарта использовать одновременно "override" и "=default", будет ли диструктор сгенерирован правильно в этом случае. И, если это корректно, можно ли считать, что именно так и желательно писать всегда, чтобы гарантировать что деструктор родителя виртуальный.
default следует читать как "сгенерировать деструктор по умолчанию для данного класса", а не "использовать уже готовый по умолчанию". Т.е. будет все ок. Тоже самое происходит, если в классе так объявлен конструктор копирования. Конструктор копирования будет сгенерирован именно для этого класса.
Здравствуйте, ksandro, Вы писали:
K>Насколько корректно с точки зрения стандарта использовать одновременно "override" и "=default", будет ли диструктор сгенерирован правильно в этом случае. И, если это корректно, можно ли считать, что именно так и желательно писать всегда, чтобы гарантировать что деструктор родителя виртуальный.
Виртуальнось деструктора, заложенная в базовом классе, не может быть отменена никакими ключевыми словами. Ключевое слово "override" не является обязательным и выражает лишь гарантию того, что функция переопределяет виртуальную функцию базового класса. "default" говорит только о том, что тело деструктора сгенерировано компилятором, а не написано программистом, и тоже никак не влияет на виртуальность. Таким образом, если деструктор базового класса объявлен виртуальным, то деструкторы всех производных классов также будут виртуальными, независимо от их оформления.
--
Не можешь достичь желаемого — пожелай достигнутого.