C++ virtual destructor. Зачем?

C c++ активно работаю больше 10 лет. Так что объьяснять в коментах как работает вируалый деструктон не нужно. Но кто-то может обьяснить почему его не сделали виртуальным по умолчанию?
В принципе компилятор сам способен розобратся когда его делать виртуальным. Или можно считать его виртаульным всегда и добавить в язык конструкцию позволяющую удалить его из таблицы виртуальных ф-ция если нужно.
Вместо этого он не виртуальный по умолчанию. Из-за него возникает куча багов у джуников, и куча дурацких вопросов на собеседованиях. Сложно привести пример другого языка програмирования с подобной конструкцией.
Может кто-то обьяснить почем у они задизайнили язык так? Сейчас даже редакторы когда создают класс из шаблона делают конструктор виртуальным... Возможно Страуструп & Co пробовали что-то сэкономить? но там реальной экономии памяти практически нету да и компиляторы способны это оптимизировать. Тоесть выбраный подход кроме проблем ничего не создает

👍НравитсяПонравилось0
В избранноеВ избранном0
LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
В принципе компилятор сам способен розобратся когда его делать виртуальным.

Чтоб узнать нужна ли виртуальность деструктору, нужно знать, есть ли у этого класса наследники. Если вы разрабатываете библиотечку и класс ваш хранится в какой-нибудь DLL, то как компилятор узнает (в момент компилирования вашего класса) будут ли наследники? Философия C++: не делать того, что не просят.

то как компилятор узнает (в момент компилирования вашего класса) будут ли наследники?
Будут ли наследники, компилятор узнает во время компиляции класса, который будет наследником.
и класс ваш хранится в какой-нибудь DLL
В DLL будет храниться экземпляр класса, который явно задать в качестве родительского, насколько я себе представляю, нельзя. А описание класса, в любом случае, будет распространяться чезер заголовочные файлы.

Но с другой стороны, тогда, объекты такого базового класса, созданные по одному и тому же описанию, но при различных этапах компиляции (одни без наследования, а другие с наследованием) будут по сути разного типа.

Философия C++: не делать того, что не просят.
Ага, можно подумать, я прошу вызывать конструктор копирования.
В DLL будет храниться экземпляр класса, который явно задать в качестве родительского, насколько я себе представляю, нельзя.
1. «экземпляр класса»? што? от объектов нельзя «наследоваться» вообще
2. код класса будет размещен в DLL, и от него точно можно наследоваться
Но с другой стороны, тогда, объекты такого базового класса, созданные по одному и тому же описанию, но при различных этапах компиляции (одни без наследования, а другие с наследованием) будут по сути разного типа.
полный бред
Ага, можно подумать, я прошу вызывать конструктор копирования.
не вызывайте. std::move
«экземпляр класса»? што?
Экземпляр класса.
от объектов нельзя «наследоваться» вообще
Ну так и я кажется о том же.
код класса будет размещен в DLL
С C# не путаете? Зачем вообще в С++ нужны заголовочные файлы?
полный бред
Да ладно, не судите строго, я только начинающий специалист.
не вызывайте
А я и не вызываю.
std::move
И его я тоже не вызываю.
Экземпляр класса.

Практически всегда под этим подразумевают объект соответствующего класса, а не определение самого класса. Уже понятно, что вы имели в виду, но терминология резко не соответствует общепринятой (и даже стандартной).

Зачем вообще в С++ нужны заголовочные файлы?

Если класс используется более чем двумя исходными файлами — смысл заголовков очевиден. А при чём тут вообще C#?

Да ладно, не судите строго, я только начинающий специалист.

:)

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

zero overhead principle: не платити за те, що не використовується. Виклик віртуальних деструкторів це таки трохи overhead; цим мотивована «логіка», що краще описувати віртуальність явно. А шукати логіки в С++ часто даремно.

Кстати, а при virtual inheritance виртуальный деструктор будет работать?
Вот допустим есть класс Top, от него отнаследовались Leaf1 и Leaf2, и от них двоих Bottom. Что будет если у Bottom деструктор не виртуальный?

Кстати, а при virtual inheritance виртуальный деструктор будет работать?
будет конечно
Что будет если у Bottom деструктор не виртуальный?
Кого вообще волнует какой у ботом деструктор?
Что будет если у Bottom деструктор не виртуальный?

Не будет. Независимо от наличия указания virtual в производном классе, виртуальность деструктора в базовом обеспечивает это для всех потомоков.

О, недавно общались на эту тему на собеседовании.

Я думаю тут сиграл принцип «мы не платим за то чего не используем». Ну и не включаем по дефолту. Да плата действительно совсем не большая, бенефиты есть, но есть кейсы где его и не надо.

+ я ещё припоминаю рассуждения на тему «вызов виртуальной функции, насколько же это дорого», возможно давным-давно были сомнения на тему того насколько это реально дёшево иметь vtbl и виртуальный вызов.

Я подозреваю что в той книге старуструпа «дизайн и эволюция» должны быть ответы на такие вопросы.

Мне кажется, в конце поста не хватает чего-то вроде

P.S. Я джва года хочу такую фичу

Когда уже раст весь этот балаган прибьет?

Ну почему, jаvа например Ц++ с многих мест выперла в свое время

не джава, а sun, которая проталкивала джаву

Ну когда то было время корпораций, сейчас скопления хипстеров тоже играют свою роль, примеры — питон, руби.

Деструктор не должен быть виртуальным по умолчанию если .ехе и .dll создаются не одним и тем же компилятором. Не знаю, как в новых стандартах 11/14 (не было надобности смотреть), но в старом не стандартизировано положение виртуального деструктора в vtbl. То есть, у одного компилятора он может находиться в ее начала, а у другого в конце. При компиляции на раннем связывании зашивается смещение от начала vtbl к положению указателя на виртуальную функцию. (процесс вызова — чтение vptr, переход на начало vtbl, переход на адрес функции, вызов) Если в .exe мы получили объект класса из загруженной dll, она могла быть скомпилена совсем другим компилятором. Следовательно, при завершающем вызове из .exe delete pOnBaseClass; будет попытка вызова виртуального деструктора оттуда, где его нет. Со всеми вытекающими последствиями... Решение в этих случаях — использование умного указателя (например shared_ptr) у которого есть конструктор, принимающий функцию, которая и будет вызывать из под указателя на базовый класс (хранящийся в shared_ptr) виртуальную функцию типа Release в виндовом СОМ-е. внутри которой delete this; который и делает то, что в обычных случаях делает виртуальный деструктор.

про использование С++ либы(даже если это длл) созданной в одном компиляторе из другого вы загнули. Это очень плохая практика. Фактически либы скомпилирование разными компиляторами можно считать несовместимимыми(за парочкой исключений) www.mingw.org/...Different_Compiler_Brands

даже если это длл
а если это .so?
в нормальных системах есть стандарты на ABI
в нормальных системах есть стандарты на ABI

В нормальных системах они и соблюдаются. Но совместить C++ ABI от MS, Borland и Watcom, мягко говоря, было нереально (когда они ещё были популярны).

Кроме ABI есть еще например передача объектов стандартной библиотеки, которая может быть либо разная в разных so-никах, либо скомпилированная с разными опциями, что может привести в рантайме к полному набору чудес.

это только для вижуалстудии дебажные/релизные билды ломают лайаут объектов

Речь не только. о релизных/дебажных. Под линуксом не стоит мешать libc++ и stdlibc++, да даже если тип библиотеки совпадает, но не совпадает какой-нибудь хитрый флаг вроде использования атомиков, то последствия будут неприятными.

Это очень плохая практика
СОМ технология как раз и была рассчитана на использование компонентов из под разных компиляторов и даже языков. Плюсы спокойно использовали компоненты, написанные на VB. Поэтому, такой вариант там приходится учитывать — в IUnknown нет виртуального деструктора. Достаточная ABI совместимость позволяет, чтобы возвращение памяти выполнялось теми же средствами, которыми она и выделялась.
Фактически либы скомпилирование разными компиляторами можно считать несовместимимыми
Intel C++, G++ и Clang C++ вполне совместимы, как пример и уживаются вместе даже в одном проекте.

Я так понимаю виртуальным по умолчанию, деструктор стоит делать только ели у класса есть виртуальные методы. Тогда другая головня начнется, то он виртуальный — то не виртуальный, ну компилятор сам будет решать (в этом плане, чем то инлайн напоминает...)
P.S. А вообще С++ это песня, я за 7 лет только начал догонять истинный смысл понятий rvalue/lvalue, как они (в С++11) еще забахали штуки 3 подтипов для rvalue(prvalue, xvalue, glvalue) и там вообще хрен разберешь. Вообщем С++ соовсем не скучный язык.

В общем не заскучаешь и всегда любого на собеседовании можно будет ниже плинтуса опустить.
Я хуеею с таких программистов. Когда американец будет пытаться какая может быть польза с такого человека, наши опускать будут.

Потому что даже если класс учавствует в иерархии не означает, что он будет уничтожаться полиморфно. Конечно, лучше бы сделать для не final-классов деструктор по умолчанию виртуальным, а чтобы сделать его не виртуальным надо было бы явно это указывать.

А зачем в С++11 union’ax мемберы могут иметь модификатор protected, несмотря на то, что юнион не может участвовать в иерархии ? :)

A C++ union is a limited form of the class type. It can contain access specifiers (public, protected, private), member data, and member functions, including constructors and destructors. It cannot contain virtual functions or static data members. It cannot be used as a base class, nor can it have base classes. The default access of members in a union is public. (MSDN). Т.е. по сути — раз это подвид класса, то для унификации.

В общем, рудимент.

Я не уверен, но скорее всего для совместимости с С и старым кодом. В С++ большинство ограничений и странных вещей этим объясняется.
Бонус к теме — у АленыСрр была статья на тему ненужности delete[] p. Тут ситуация даже хуже чем с деструктором, потому что дело в какой-то совсем не существенной экономии, а при неправильном удалении получаем еще и undefined behavior.

потому что С++ для упырей
не принимайте на свой счет но у меня от него децел оторопь всегда была
уж слишком там нет никаких барьеров для упоротых програмистов и средних девов от него надо шваброй со ссаной тряпкой отгонять

«Упырь» и «упоротый программист», я так понимаю, означают разные понятия в данном случае, да? Типа для одних, но не для других. Пытаюсь определить, кто я.

ИМХО, упоротый это если ты используешь шаблонное метапрограммирование в стиле наркомана Александреску.

По шаблонам прошёл на упоротого. С виртуальным наследованием в продакшне не баловался.

скорее когда используешь Boost.Preprocessor для метапрограммирования
а шаблоны — это просто

Или можно считать его виртаульным всегда
даже для PODов? совместимость с сями отвалится
Из-за него возникает куча багов у джуников
это у них стандартное состояние
но там реальной экономии памяти практически нету
омг, указатель на таблицу виртуальных функций + сама таблица (+ потеря standard layout’ности + один уровень indirect’а при вызове деструктора)
если вам кажется что одна таблица виртуальных функций фигня, то что будет если класс окажется шаблонным?
да и компиляторы способны это оптимизировать.
щито????
В принципе компилятор сам способен розобратся когда его делать виртуальным.
Тогда теряется контроль над кодом, компилятор не должен сам разбираться. Сейчас и так gcc генерирует код для трёх разных деструкторов для каждого класса.
Возможно Страуструп & Co пробовали что-то сэкономить?
www.stroustrup.com/...bs_faq2.html#virtual-dtor

практически во всех ОО языках компилятор с этим прекрасно справляется. Пресловутого контроля над кодом на практике нет да и он особо не нужен

при всем уважении к Страуструпу

Возможно Страуструп & Co пробовали что-то сэкономить?
www.stroustrup.com/...bs_faq2.html#virtual-dtor
Why are destructors not virtual by default?
Because many classes are not designed to be used as base classes. Virtual functions make sense only in classes meant to act as interfaces to objects of derived classes (typically allocated on a heap and accessed through pointers or references).
So when should I declare a destructor virtual? Whenever the class has at least one virtual function. Having virtual functions indicate that a class is meant to act as an interface to derived classes, and when it is, an object of a derived class may be destroyed through a pointer to the base.

звучит как описание очень корявого решения. Если от класса нельзя порождать другие классы — это должно обозначатся на уровне класса (как final class в джаве) а тут получается что деструктор не виртуальный и при этом ничто не мешает отнаследоватся от класса.

Гораздо логичней было бы иметь деструктор вирутальным и например gcc atttributes если он нужен не виртуальный

Вроде ж он вначале налабал C with classes, где никакого полиморфизма не было, и соответственно виртуальных деструкторов тоже, а потом старик видать поленился мигрировать код, и так и осталось.

Пресловутого контроля над кодом на практике нет да и он особо не нужен
В этом вся проблема. Учись контролировать код, Люк.
звучит как описание очень корявого решения.
Ну ещё бы. За что я люблю ДОУ, так это за то, что у местных С++ программеров даже Страуструп не авторитет %)

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

который ничего не оптимизирует
Указатель на таблицу виртуальных методов, Карл!

Ладно не верьте авторитетам. Но вспомните КОМки, у них ведь нет виртуальных деструкторов, но есть виртуальные методы, подобный дизайн работает уже много лет, и работает хорошо.

а зачем слепо верить авторитетам?
Мой опыт показывает, что как раз такой ход мыслей зачастую приводит к плачевным последствиям, а сами представители этим ещё и свою безграмотность оправдывают.
Считаю, что вера в авторитеты — этап становления профессионала. Этап, который нельзя перескакивать.
Прошу извинить за оффтоп.
Если от класса нельзя порождать другие классы — это должно обозначатся на уровне класса (как final class в джаве)
В C++11 есть же final классы, которые не могут быть базовыми.

Помимо наследования интерфейса (где нам нужен динамический полиморфизм) есть наследование реализации, когда виртуальных методов может и не быть. Ну и про POD и final уже сказали выше.

So when should I declare a destructor virtual? Whenever the class has at least one virtual function.
Почему бы деструктору автоматически не стать виртуальным, когда в классе есть хотя бы один виртуальный метод? А так получается, что это ложится на плечи программиста: не забыть дописать ключевое virtual деструктору, когда добавил виртуальный метод, при этом никакого смысла оставлять деструктор невиртуальным нет, это по сути ошибка программиста.

Quote of the day: In boost 1.53 I count 72 virtual destructors out of 494 %)

Подписаться на комментарии