Сообщение Re: Виртуальный деструктор от 06.12.2016 19:48
Изменено 06.12.2016 19:53 N. I.
ksandro:
K>Насколько корректно с точки зрения стандарта использовать одновременно "override" и "=default"
Я не видел, чтоб такое где-то запрещалось.
K>будет ли диструктор сгенерирован правильно в этом случае.
Он будет такой же, как если бы его определили так:
(раз деструктор виртуальный, значит, тривиальным ему по-любому не быть).
Зачем производному классу нужна эта гарантия? Правильное удаление — это забота того, кто владеет объектом. Владелец может использовать проверку виртуального деструктора, если ему так спокойнее. Например, можно сделать аналог std::unique_ptr<T>, который будет требовать std::has_virtual_destructor<T>::value == true при любой попытке подсунуть ему в конструктор указатель на производный от T тип:
Пока кто-то не начнёт передавать владение через "небезопасные" указатели (или использовать плохие сторонние deleter-ы), unique_ptr_safe будет удалять только то, что умеет, а на просьбы получить во владение объект, который он удалять не умеет, будет отвечать ошибкой компиляции.
С реализацией через косвенный вызов правильного delete можно вообще не обращать внимание на наличие виртуального деструктора:
Лениво сейчас расписывать реализацию unique_ptr_safe и unique_ptr_universal — там, в общем-то, и так всё очевидно: надо использовать шаблонные конструкторы и в них в первом случае проверять std::has_virtual_destructor<T>::value, а в другом случае сохранять указатель на соответствующую специализацию шаблонной функции, которая потом позовёт нужный deleter.
K>Насколько корректно с точки зрения стандарта использовать одновременно "override" и "=default"
Я не видел, чтоб такое где-то запрещалось.
K>будет ли диструктор сгенерирован правильно в этом случае.
Он будет такой же, как если бы его определили так:
~Child() override {}
(раз деструктор виртуальный, значит, тривиальным ему по-любому не быть).
И, если это корректно, можно ли считать, что именно так и желательно писать всегда, чтобы гарантировать что деструктор родителя виртуальный.
Зачем производному классу нужна эта гарантия? Правильное удаление — это забота того, кто владеет объектом. Владелец может использовать проверку виртуального деструктора, если ему так спокойнее. Например, можно сделать аналог 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())' in dtor
unique_ptr_universal<B> ubmd1 = make_unique_universal<D1>(); // OK: invokes 'delete static_cast<D1 *>(get())' 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"
Я не видел, чтоб такое где-то запрещалось.
K>будет ли диструктор сгенерирован правильно в этом случае.
Он будет такой же, как если бы его определили так:
(раз деструктор виртуальный, значит, тривиальным ему по-любому не быть).
K>И, если это корректно, можно ли считать, что именно так и желательно писать всегда, чтобы гарантировать что деструктор родителя виртуальный.
Зачем производному классу нужна эта гарантия? Правильное удаление — это забота того, кто владеет объектом. Владелец может использовать проверку виртуального деструктора, если ему так спокойнее. Например, можно сделать аналог std::unique_ptr<T>, который будет требовать std::has_virtual_destructor<T>::value == true при любой попытке подсунуть ему в конструктор указатель на производный от T тип:
Пока кто-то не начнёт передавать владение через "небезопасные" указатели (или использовать плохие сторонние deleter-ы), unique_ptr_safe будет удалять только то, что умеет, а на просьбы получить во владение объект, который он удалять не умеет, будет отвечать ошибкой компиляции.
С реализацией через косвенный вызов правильного delete можно вообще не обращать внимание на наличие виртуального деструктора:
Лениво сейчас расписывать реализацию unique_ptr_safe и unique_ptr_universal — там, в общем-то, и так всё очевидно: надо использовать шаблонные конструкторы и в них в первом случае проверять std::has_virtual_destructor<T>::value, а в другом случае сохранять указатель на соответствующую специализацию шаблонной функции, которая потом позовёт нужный deleter.
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())' in dtor
unique_ptr_universal<B> ubmd1 = make_unique_universal<D1>(); // OK: invokes 'delete static_cast<D1 *>(get())' 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.