Информация об изменениях

Сообщение Re: Виртуальный деструктор от 06.12.2016 19:48

Изменено 06.12.2016 19:53 N. I.

ksandro:

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>будет ли диструктор сгенерирован правильно в этом случае.


Он будет такой же, как если бы его определили так:

~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.