Виртуальный деструктор с виртуальными членами в C++11

В этих слайдах о стандарте C++11/14 на слайде 15 автор пишет, что "многие классические правила кодирования больше не применимы" в C++11. Он предлагает список из трех примеров, и я согласен с правилом трех и управлением памятью.

Однако его второй пример - "Виртуальный деструктор с виртуальными членами" (только это). Что это значит? Я знаю, что нужно объявить виртуальным деструктором базового класса, чтобы вызвать правильный деструктор, если у нас есть что-то вроде

Base *b = new Derived;
...
delete b;

Это хорошо объясняется здесь: когда использовать виртуальные деструкторы?

Но бесполезно ли сейчас в C++ 11 объявлять виртуальным свой деструктор, если у вас есть виртуальные члены?

3 ответа

Решение

Как автор слайдов я постараюсь уточнить.

Если вы пишете код, явно выделяя Derived экземпляр с new и уничтожить его delete используя указатель базового класса, то вам нужно определить virtual деструктор, иначе вы в конечном итоге не полностью уничтожить Derived пример. Тем не менее, я рекомендую воздержаться от new а также delete полностью и использовать исключительно shared_ptr для ссылки на выделенные в куче полиморфные объекты, такие как

shared_ptr<Base> pb=make_shared<Derived>();

Таким образом, общий указатель отслеживает оригинальный деструктор, который будет использоваться, даже если shared_ptr<Base> используется для его представления. Однажды последний ссылающийся shared_ptr выходит из области видимости или сбрасывается, ~Derived() будет вызван и память освобождена. Поэтому вам не нужно делать ~Base() виртуальная.

unique_ptr<Base> а также make_unique<Derived> не предоставляют эту функцию, потому что они не обеспечивают механику shared_ptr по отношению к удалителю, поскольку уникальный указатель намного проще и нацелен на минимальные издержки и, следовательно, не хранит указатель дополнительной функции, необходимый для удалителя. С unique_ptr функция удаления является частью типа и, таким образом, uniqe_ptr со средством удаления, ссылающимся на ~Derived не будет совместим с unique_ptr<Base> используя средство удаления по умолчанию, что в любом случае было бы неправильно для производного экземпляра, если ~Base не был виртуальным

Отдельные предложения, которые я делаю, предназначены для того, чтобы им было легко следовать и следовать всем вместе. Они пытаются создать более простой код, позволяя осуществлять управление всеми ресурсами компонентами библиотеки и сгенерированным компилятором кодом.

Определение (виртуального) деструктора в классе запрещает предоставленный компилятором оператор конструктора перемещения / присваивания и может также запретить предоставляемый компилятором оператор конструктора / присваивания копии в будущих версиях C++. Воскресить их стало легко с =default, но все еще выглядит как много стандартного кода. И лучший код - это код, который вам не нужно писать, потому что он не может быть неправильным (я знаю, что есть исключения из этого правила).

Подводя итог "Не определяйте (виртуальный) деструктор" как следствие моего "Правила ноля":

Всякий раз, когда вы проектируете полиморфную (OO) иерархию классов в современном C++ и хотите / должны размещать ее экземпляры в куче и обращаться к ним через указатель базового класса, используйте make_shared<Derived>() создать их экземпляр и shared_ptr<Base> держать их рядом. Это позволяет сохранить "Правило нуля".

Это не значит, что вы должны размещать все полиморфные объекты в куче. Например, определение функции, принимающей (Base&) как параметр, может вызываться с локальным Derived переменная без проблем и будет вести себя полиморфно, относительно виртуальных функций-членов Base,

На мой взгляд, динамический ОО-полиморфизм сильно используется во многих системах. Мы не должны программировать как Java, когда мы используем C++, если только у нас нет проблемы, где динамический полиморфизм с выделенными объектами кучи является правильным решением.

Я думаю, что это связано с "правилом нуля", упомянутым в другом месте презентации.

Если у вас есть только автоматические переменные-члены (т.е. используйте shared_ptr или же unique_ptr для членов, которые в противном случае были бы необработанными указателями), тогда вам не нужно писать собственные конструкторы копирования или перемещения или операторы присваивания - значения по умолчанию, предоставляемые компилятором, будут оптимальными. При инициализации в классе вам также не нужен конструктор по умолчанию. И, наконец, вам вообще не нужно писать деструктор, виртуальный или нет.

Связанная бумага показывает соответствующий код:

std::unique_ptr<Derived> { new Derived };

Сохраненный удалитель std::default_delete<Derived>, который не требует Base::~Base быть виртуальным.

Теперь вы можете переместить это в unique_ptr<Base> и это также переместит std::default_delete<Derived> без преобразования его в std::default_delete<Base> ,

Чтобы ответить на конкретный вопрос...

Но разве сейчас в C++11 бесполезно объявлять виртуальный деструктор, если у вас есть виртуальные члены?

Потребность в виртуальном деструкторе НЕ изменилась в основном языке C++11. Вы должны объявить свой деструктор как виртуальный, если вы удаляете производный объект с помощью базового указателя.

Утверждение на слайде создает впечатление, что C++11 каким-то образом изменил поведение по отношению к виртуальному деструктору, что не так. Как пояснил автор, это применимо только при использованииshared_ptr. Но то, что виртуальный деструктор по-прежнему требуется (кроме использованияshared_ptr) разбавляется длинным объяснением.

Другие вопросы по тегам