C++ virtual destructor. Зачем?
C c++ активно работаю больше 10 лет. Так что объьяснять в коментах как работает вируалый деструктон не нужно. Но кто-то может обьяснить почему его не сделали виртуальным по умолчанию?
В принципе компилятор сам способен розобратся когда его делать виртуальным. Или можно считать его виртаульным всегда и добавить в язык конструкцию позволяющую удалить его из таблицы виртуальных ф-ция если нужно.
Вместо этого он не виртуальный по умолчанию. Из-за него возникает куча багов у джуников, и куча дурацких вопросов на собеседованиях. Сложно привести пример другого языка програмирования с подобной конструкцией.
Может кто-то обьяснить почем у они задизайнили язык так? Сейчас даже редакторы когда создают класс из шаблона делают конструктор виртуальным... Возможно Страуструп & Co пробовали что-то сэкономить? но там реальной экономии памяти практически нету да и компиляторы способны это оптимизировать. Тоесть выбраный подход кроме проблем ничего не создает
57 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарівЧтоб узнать нужна ли виртуальность деструктору, нужно знать, есть ли у этого класса наследники. Если вы разрабатываете библиотечку и класс ваш хранится в какой-нибудь DLL, то как компилятор узнает (в момент компилирования вашего класса) будут ли наследники? Философия C++: не делать того, что не просят.
В DLL будет храниться экземпляр класса, который явно задать в качестве родительского, насколько я себе представляю, нельзя. А описание класса, в любом случае, будет распространяться чезер заголовочные файлы.
Но с другой стороны, тогда, объекты такого базового класса, созданные по одному и тому же описанию, но при различных этапах компиляции (одни без наследования, а другие с наследованием) будут по сути разного типа.
Ага, можно подумать, я прошу вызывать конструктор копирования.2. код класса будет размещен в DLL, и от него точно можно наследоваться полный бред не вызывайте. std::move
Ну так и я кажется о том же. С C# не путаете? Зачем вообще в С++ нужны заголовочные файлы? Да ладно, не судите строго, я только начинающий специалист. А я и не вызываю.
И его я тоже не вызываю.
Практически всегда под этим подразумевают объект соответствующего класса, а не определение самого класса. Уже понятно, что вы имели в виду, но терминология резко не соответствует общепринятой (и даже стандартной).
Если класс используется более чем двумя исходными файлами — смысл заголовков очевиден. А при чём тут вообще C#?
:)
zero overhead principle: не платити за те, що не використовується. Виклик віртуальних деструкторів це таки трохи overhead; цим мотивована «логіка», що краще описувати віртуальність явно. А шукати логіки в С++ часто даремно.
Кстати, а при virtual inheritance виртуальный деструктор будет работать?
Вот допустим есть класс Top, от него отнаследовались Leaf1 и Leaf2, и от них двоих Bottom. Что будет если у Bottom деструктор не виртуальный?
Кого вообще волнует какой у ботом деструктор?
Не будет. Независимо от наличия указания virtual в производном классе, виртуальность деструктора в базовом обеспечивает это для всех потомоков.
О, недавно общались на эту тему на собеседовании.
Я думаю тут сиграл принцип «мы не платим за то чего не используем». Ну и не включаем по дефолту. Да плата действительно совсем не большая, бенефиты есть, но есть кейсы где его и не надо.
+ я ещё припоминаю рассуждения на тему «вызов виртуальной функции, насколько же это дорого», возможно давным-давно были сомнения на тему того насколько это реально дёшево иметь vtbl и виртуальный вызов.
Я подозреваю что в той книге старуструпа «дизайн и эволюция» должны быть ответы на такие вопросы.
Мне кажется, в конце поста не хватает чего-то вроде
Когда уже раст весь этот балаган прибьет?
никогда
Ну почему, 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
в нормальных системах есть стандарты на ABI
В нормальных системах они и соблюдаются. Но совместить C++ ABI от MS, Borland и Watcom, мягко говоря, было нереально (когда они ещё были популярны).
Кроме ABI есть еще например передача объектов стандартной библиотеки, которая может быть либо разная в разных so-никах, либо скомпилированная с разными опциями, что может привести в рантайме к полному набору чудес.
это только для вижуалстудии дебажные/релизные билды ломают лайаут объектов
Речь не только. о релизных/дебажных. Под линуксом не стоит мешать libc++ и stdlibc++, да даже если тип библиотеки совпадает, но не совпадает какой-нибудь хитрый флаг вроде использования атомиков, то последствия будут неприятными.
Я так понимаю виртуальным по умолчанию, деструктор стоит делать только ели у класса есть виртуальные методы. Тогда другая головня начнется, то он виртуальный — то не виртуальный, ну компилятор сам будет решать (в этом плане, чем то инлайн напоминает...)
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 для метапрограммирования
а шаблоны — это просто
если вам кажется что одна таблица виртуальных функций фигня, то что будет если класс окажется шаблонным? щито????
www.stroustrup.com/...bs_faq2.html#virtual-dtor
практически во всех ОО языках компилятор с этим прекрасно справляется. Пресловутого контроля над кодом на практике нет да и он особо не нужен
при всем уважении к Страуструпу
звучит как описание очень корявого решения. Если от класса нельзя порождать другие классы — это должно обозначатся на уровне класса (как final class в джаве) а тут получается что деструктор не виртуальный и при этом ничто не мешает отнаследоватся от класса.
Гораздо логичней было бы иметь деструктор вирутальным и например gcc atttributes если он нужен не виртуальный
Вроде ж он вначале налабал C with classes, где никакого полиморфизма не было, и соответственно виртуальных деструкторов тоже, а потом старик видать поленился мигрировать код, и так и осталось.
Ну ещё бы. За что я люблю ДОУ, так это за то, что у местных С++ программеров даже Страуструп не авторитет %)
а зачем слепо верить авторитетам?
Он при дизайне языка выбрал вариант который ничего не оптимизирует и создает кучу возможностей написать багнутый код
Ладно не верьте авторитетам. Но вспомните КОМки, у них ведь нет виртуальных деструкторов, но есть виртуальные методы, подобный дизайн работает уже много лет, и работает хорошо.
Считаю, что вера в авторитеты — этап становления профессионала. Этап, который нельзя перескакивать.
Прошу извинить за оффтоп.
Помимо наследования интерфейса (где нам нужен динамический полиморфизм) есть наследование реализации, когда виртуальных методов может и не быть. Ну и про POD и final уже сказали выше.
Quote of the day: In boost 1.53 I count 72 virtual destructors out of 494 %)