Комментарии 51
Хотелось бы уточнить по поводу:
> Вывод: делать виртуальные методы публичными не очень хорошо.
Это вы имеете ввиду конечные классы, которыми пользуется пользователь, или вообще всегда? Ведь абстрактные классы никто не запрещал вроде.
> Вывод: делать виртуальные методы публичными не очень хорошо.
Это вы имеете ввиду конечные классы, которыми пользуется пользователь, или вообще всегда? Ведь абстрактные классы никто не запрещал вроде.
0
<gramar_nazi>
У Вас в заголовке запятая пропущена
</gramar_nazi>
У Вас в заголовке запятая пропущена
</gramar_nazi>
-1
Честно говоря, в любой разработке один из самых неуловимых, но важных моментов — соблюдать меру. Если вы пишете корпоративное приложение, которое и поддерживаете — то да, подход вашей «иномарки» оправдан, т.к. позволяет вам иметь контроль над приложением и держать архитектуру чистой.
Если же, например, такой код встречается в публичных библиотеках, то подход вряд ли будет оправдан. Не все приложения, использующие ее, захотят следовать всем вашим правилам. Плюс к тому, если используется несколько библиотек, то следование правилам каждой из них может превратить жизнь в ад, с учетом что они нередко противоречат друг другу.
Так что… Да, есть минусы, есть плюсы. Но конкретный выбор далеко не столь однозначен и прост.
Если же, например, такой код встречается в публичных библиотеках, то подход вряд ли будет оправдан. Не все приложения, использующие ее, захотят следовать всем вашим правилам. Плюс к тому, если используется несколько библиотек, то следование правилам каждой из них может превратить жизнь в ад, с учетом что они нередко противоречат друг другу.
Так что… Да, есть минусы, есть плюсы. Но конкретный выбор далеко не столь однозначен и прост.
0
> Любопытно было бы узнать, а какой ответ ожидали они?
Вероятно, достаточно бы было ответа про:
1) В C++ main должен возвращать int
2) В списках инициализаторов Bar должен быть Foo(что-то там), поскольку у Foo нет конструктора без обязательных параметров.
3) В Foo должен быть конструктор копирования
4) В Bar должен быть конструктор неявного преобразования в Foo (привет valgrind)
5) Для реализации пункта 4 скорее всего понадобится указать спецификатор доступа к родительскому классу class Bar: public Foo
6) В ~Foo и ~Bar должны быть delete[], а не delete
7) Проверки на успешность выделения памяти? fputs("Доставьте ваш комп в музей", stderr); Тут почти наверняка они хотели увидеть что-то про auto_ptr и SFINAE
8) В качестве параметров конструкторов должно быть size_t, а не int (привет статические анализаторы)
9) Виртуальные деструкторы: см. пост.
Хотя, быть может, они хотели увидеть переделанный код на std::vector и отсутствие элементов-данных с одинаковыми именами. Чёрт их разберёт.
+9
Даже по первому пункту можно сказать много больше. В общем, по однму только первому вопросу можно докторскую защищать :-) Такую добротную, с историей вопроса, обзором литературы и стандартов, описанием существующих решений и тенденций.
-2
> fputs(«Доставьте ваш комп в музей», stderr);
никогда не вылезали за 2Gb в 32-битном процессе? бывают такие задачи.
никогда не вылезали за 2Gb в 32-битном процессе? бывают такие задачи.
0
НЛО прилетело и опубликовало эту надпись здесь
Для выполнения предложенной задачи необходимо и достаточно 500 байт на классической 32-битной платформе. Так что точно в музей =).
Ещё: к пункту 8 скорее всего стоит дописать замену int *i на int32_t *i.
Задача-минимум состоит в том, чтобы код компилировался компиляторами, работающими по стандарту, не производил утечек и обеспечивал одинаковую работу на разных платформах/архитектурах.
Ещё: к пункту 8 скорее всего стоит дописать замену int *i на int32_t *i.
Задача-минимум состоит в том, чтобы код компилировался компиляторами, работающими по стандарту, не производил утечек и обеспечивал одинаковую работу на разных платформах/архитектурах.
0
3: в Foo по той же причине должен быть ещё и оператор присваивания. И так же конструктор копирования и оператор присваивания должны быть в Bar.
7: operator new кидает исключение при невозможности выделить память.
7: operator new кидает исключение при невозможности выделить память.
0
На картинке явно видно, что строение не жилым, не промышленным и никаким небыло, построили и сразу поломали?
— Что это за семиэтажная херня?
— Дак вы же сами…
— Экскаватор на нее поставили и чтоб я через 3 дня ее не видел!
— Что это за семиэтажная херня?
— Дак вы же сами…
— Экскаватор на нее поставили и чтоб я через 3 дня ее не видел!
0
В общем RAII во все поля…
0
Не соглашусь с таким шаблонным разделением: «маленькое и ненадолго — публичный виртуальный» против «большое и всерьёз — защищённый невиртуальный». Есть паттерны, опирающиеся на полиморфный деструктор, state например. И Герб Саттер в своей заметке не так категоричен. Нужно просто понимать что виртуальный и невиртуальный деструктор — предназначены для двух разных вариантов использования базового класса.
+4
Как-то уж очень много натяжек
Вывод: полиморфное удаление — подозрительная штука.
Вывод сделан на основании того, что владеющий класс всегда контролирует процесс создания объектов — то есть что не бывает сложных объектов, созданных на классе-фабрике и переданных во владение в нашу подсистему. Ага.
Вывод: делать виртуальные методы публичными не очень хорошо.
Замечательно — особенно если мы реализуем COM-интерфейс с какими-нибудь тривиальными свойствами. Что там надо городить вместо простой реализации
Вывод: прежде, чем сделать первый виртуальный метод, можно на минуту задуматься: а оно нам точно надо?
Ограничение по поводу union'ов конечно нас пугает до смерти, а уж использование виртуальных вызовов если боремся за скорость проконтролировать никак нельзя. Ага.
Предложенное переусложнение, IMHO, не решает никаких проблем. Так что лучше пользоваться виртуальными деструкторами и не мучиться бессонницей на эту тему.
Вывод: полиморфное удаление — подозрительная штука.
Вывод сделан на основании того, что владеющий класс всегда контролирует процесс создания объектов — то есть что не бывает сложных объектов, созданных на классе-фабрике и переданных во владение в нашу подсистему. Ага.
Вывод: делать виртуальные методы публичными не очень хорошо.
Замечательно — особенно если мы реализуем COM-интерфейс с какими-нибудь тривиальными свойствами. Что там надо городить вместо простой реализации
virtual int GetTrivialField() const = 0
?Вывод: прежде, чем сделать первый виртуальный метод, можно на минуту задуматься: а оно нам точно надо?
Ограничение по поводу union'ов конечно нас пугает до смерти, а уж использование виртуальных вызовов если боремся за скорость проконтролировать никак нельзя. Ага.
Предложенное переусложнение, IMHO, не решает никаких проблем. Так что лучше пользоваться виртуальными деструкторами и не мучиться бессонницей на эту тему.
+13
> автоматизировать процесс удаления объектов (автоматические переменные, auto_ptr и множество подобных средств),
Давайте, расскажите мне как использовать auto_ptr на интерфейс без полиморфного удаления
> Вывод: полиморфное удаление — подозрительная штука.
Вывод высосан из пальца, аргументов нет. NVI, brige — это все хорошо, но только не надо забывать что деструктор — это чисто техническая вещь, к обязанностям интерфейса класса он как правило не имеет ни малейшего отношения.
> приблизить друг другу операции создания и удаления и таким образом сделать код более простым, стройным.
Да что вы говорите. Каким образом код станет проще и стройнее? Создание и удаление в современном C++ — это RAII, new и delete действительно рядом написаны в коде, но вот только в большом количестве случаев — удаление полиморфное: shared_ptr(new Service); при этом создание и удаление производится как правило в разных контекстах
> Например, такие объекты нельзя использовать в объединениях.
Еще есть ограничения, кроме непереносимого юниона? дальше вроде о переносе на другие платформы упоминается.
Итого: стоящих аргументов не видно, примеров плохого кода с полиморфным удалением нет.
Давайте, расскажите мне как использовать auto_ptr на интерфейс без полиморфного удаления
> Вывод: полиморфное удаление — подозрительная штука.
Вывод высосан из пальца, аргументов нет. NVI, brige — это все хорошо, но только не надо забывать что деструктор — это чисто техническая вещь, к обязанностям интерфейса класса он как правило не имеет ни малейшего отношения.
> приблизить друг другу операции создания и удаления и таким образом сделать код более простым, стройным.
Да что вы говорите. Каким образом код станет проще и стройнее? Создание и удаление в современном C++ — это RAII, new и delete действительно рядом написаны в коде, но вот только в большом количестве случаев — удаление полиморфное: shared_ptr(new Service); при этом создание и удаление производится как правило в разных контекстах
> Например, такие объекты нельзя использовать в объединениях.
Еще есть ограничения, кроме непереносимого юниона? дальше вроде о переносе на другие платформы упоминается.
Итого: стоящих аргументов не видно, примеров плохого кода с полиморфным удалением нет.
+6
Вывод: делать виртуальные методы публичными не очень хорошо.
Насчет обычных методов спорить не буду, а вот деструктор практически всегда должен быть виртуальным, так как теоретически у вас нету 100-процентной уверенности в том, что кто-нибудь не захочет наследовать от вашего класса.
На мой взгляд, вывод из статьи должен быть таким — если в классе есть хотя бы один виртуальный метод, то и деструктор должен быть обязательно виртуальным. Если же в классе виртуальных методов нету, то необходимо в комментарии пояснить, что он не предназначен для наследования. Если же все-таки может возникнуть необходимость наследовать от класса, деструктор должен быть виртуальным. Это покрывает случаи использования класса другими людьми или вами же, но в другом проекте или контексте.
Если класс будет использоваться только в пределах одного проекта, тогда правила устанавливаются из соображений целесообразности для данного конкретного проекта.
+4
> если в классе есть хотя бы один виртуальный метод, то и деструктор должен быть обязательно виртуальным
Тогда уж лучше так: еcли у наследника есть деструктор, то у базового класса деструктор должен быть виртуальным. Только вот беда — базовый класс заранее не знает, какие от него отнаследуются наслденики.
> необходимо в комментарии пояснить, что он не предназначен для наследования
Во-первых, класс не предназначен для полиморфного удаления. А наследовать можно и даже нужно.
Во-вторых, обратите внимание, что если вы делаете деструктор защищённым, то никаких комментариев не понадобится. Код из первого примера просто не скомпилируется.
Тогда уж лучше так: еcли у наследника есть деструктор, то у базового класса деструктор должен быть виртуальным. Только вот беда — базовый класс заранее не знает, какие от него отнаследуются наслденики.
> необходимо в комментарии пояснить, что он не предназначен для наследования
Во-первых, класс не предназначен для полиморфного удаления. А наследовать можно и даже нужно.
Во-вторых, обратите внимание, что если вы делаете деструктор защищённым, то никаких комментариев не понадобится. Код из первого примера просто не скомпилируется.
0
в java все методы виртуальные
и ничего — живут люди
и ничего — живут люди
+4
либо следует удалять объекты где-то не слишком далеко от точки их здания
исправьте здания на создания
0
Один из случаев, необходимо использование виртуальных деструкторов — придание виртуальности оператору delete, определенному локально. Язык C++ не является полным, поэтому приходится так неочевидно добиваться virtual operator delete.
0
По поводу вопроса в вакансии Яндекса — тип подкласса из-за private наследования(т.е. наследование реализации) не является полиморфным и не приводится к базовому классу, соответственно то что мы обсуждаем немного не то.
В STL практически нет виртуальных функций и деструкторов потому что это часто не нужно. И зачем использовать контейнер STL как полиморфный тип?
«Деструкторы необходимо делать виртуальными.» — так говорят всегда всем студентам и школьникам при первом знакомстве с С++. В этом контексте, я полностью согласен с этим утверждением — если сделаешь деструктор виртуальным, то хуже никогда не будет. Понимая как он работает, ты вряд ли ошибешься.
Но отсутствие виртуальности деструктора позволяет:
— сделать базовый класс POD-типом
— сделать С-совместимый тип (иногда)
— уменьшить растраты на VMT(иногда, с увеличением производительности)
— использовать объект без его удаления в callback-ах
Если забыть про разные С-подобные штуки, то вопрос точнее должен стоять так — А должны ли объекты класса быть полиморфно удаляемыми?
Для COM-объектов это необходимо, вспомните про delete this в Release().
В STL практически нет виртуальных функций и деструкторов потому что это часто не нужно. И зачем использовать контейнер STL как полиморфный тип?
«Деструкторы необходимо делать виртуальными.» — так говорят всегда всем студентам и школьникам при первом знакомстве с С++. В этом контексте, я полностью согласен с этим утверждением — если сделаешь деструктор виртуальным, то хуже никогда не будет. Понимая как он работает, ты вряд ли ошибешься.
Но отсутствие виртуальности деструктора позволяет:
— сделать базовый класс POD-типом
— сделать С-совместимый тип (иногда)
— уменьшить растраты на VMT(иногда, с увеличением производительности)
— использовать объект без его удаления в callback-ах
Если забыть про разные С-подобные штуки, то вопрос точнее должен стоять так — А должны ли объекты класса быть полиморфно удаляемыми?
Для COM-объектов это необходимо, вспомните про delete this в Release().
+1
Вот этот тонкий момент: «сделать базовый класс POD-типом» + «сделать С-совместимый тип» + уменьшить растраты на VMT меня всегда волновал. А оно надо? Если идёт борьба за байты и миллисекунды, может просто написать критически важные участки кода на чистом с, или вообще на асме?
А последний пункт («использовать объект без его удаления в callback-ах») не понял, объясните, пожалуйста.
А последний пункт («использовать объект без его удаления в callback-ах») не понял, объясните, пожалуйста.
0
но ведь С++ удобнее, и в нем есть много бесплатных удобств…
а некоторые удобства еще и несут дополнительную производительность, шаблоны например…
а некоторые удобства еще и несут дополнительную производительность, шаблоны например…
0
> Скажем, если вы создаёте объект (в куче) в конструкторе некого контейнера,
> то уместно удалить этот объект в деструкторе того же контейнера.
> Обратите внимание, что в данном случае в полиморфном удалении нет никакой необходимости.
Обращаю внимание, что из первого предложения следует отрицание второго. Если то, что создаётся в конструкторе логично освободить в деструкторе, то если в потомках что-то будет создаваться (что довольно ожидаемо), то деструкторы потомков должны вызываться и деструктор должен быть виртуальным.
Более логичный вывод — деструктор не должен быть виртуальным только в тех случаях когда потомки будут отличаться только поведением.
> то уместно удалить этот объект в деструкторе того же контейнера.
> Обратите внимание, что в данном случае в полиморфном удалении нет никакой необходимости.
Обращаю внимание, что из первого предложения следует отрицание второго. Если то, что создаётся в конструкторе логично освободить в деструкторе, то если в потомках что-то будет создаваться (что довольно ожидаемо), то деструкторы потомков должны вызываться и деструктор должен быть виртуальным.
Более логичный вывод — деструктор не должен быть виртуальным только в тех случаях когда потомки будут отличаться только поведением.
0
Давайте вспомним, для кого создаются публичные методы. Они определяют интерфейс класса и создаются для тех, кто будет использовать класс.
А для чего существуют виртуальные методы? Правильно — для настройки поведения класса. То есть для тех, кто будет расширять функциональность класса.
Виртуальные методы служат в основном для декларации внешних интерфейсов классов. А они как раз публичные. А вот непубличные виртуальные методы как правило говорят о использовании наследования реализации, а не наследовании интерфейсов. Что, как правило, есть зло, т.к. пораждает сильную связность.
0
Нет. Виртуальные методы могут использоваться для декларации интерфейсов. Они ещё много для чего могут использоваться. Но служат они всё же не для этого.
И совершенно не понятно, как наличие не публичных виртуальных методов может помешать наследовать интерфейс?
Я всё же призываю не смешивать публичность и виртуальность. Это вещи не связанные, хотя и могут использоваться одновременно.
И совершенно не понятно, как наличие не публичных виртуальных методов может помешать наследовать интерфейс?
Я всё же призываю не смешивать публичность и виртуальность. Это вещи не связанные, хотя и могут использоваться одновременно.
+1
На мой взгляд, для описания интерфейса и существуют интерфейсы, т.е. абстрактные виртуальные классы, в которых деструктор не описывается, он там просто не нужен. А вот если уж в системе требуется полиморфность, временами без неё можно такого нагородить, что будет только хуже. Не вижу в этом приступления, ну да, будет работать чуть медленее и что в этом такого? Да и ранняя оптимизация редко приводит к хорошим результатам (путь то качество оптимизации или затраченное на неё время).
Я по максимуму стараюсь отказываться от полиморфизма в сторону изменения поведения классов с использованием стратегий поведения, но тут сразу появляется минус, в том, что с разными стратегиями не сохранить объекты в одном списке (разве что использовать небезопасные решения, что просто неприемлемо).
Если перейти именно к архитектуре, то хотелось бы задать вопрос, как реализовать работу плагинов без полиморфизма, как реализовать работу с различными объектами на карте (если конкретно по вакансии)?
Использовать case?
Высказывание относительно того, что использовать открытый виртуальный метод, это на мой взгляд тоже самое, что и зачем использовать руль в машине, машина нужна для того, чтобы ехать, а не для того, чтобы рулить.
Я по максимуму стараюсь отказываться от полиморфизма в сторону изменения поведения классов с использованием стратегий поведения, но тут сразу появляется минус, в том, что с разными стратегиями не сохранить объекты в одном списке (разве что использовать небезопасные решения, что просто неприемлемо).
Если перейти именно к архитектуре, то хотелось бы задать вопрос, как реализовать работу плагинов без полиморфизма, как реализовать работу с различными объектами на карте (если конкретно по вакансии)?
Использовать case?
Высказывание относительно того, что использовать открытый виртуальный метод, это на мой взгляд тоже самое, что и зачем использовать руль в машине, машина нужна для того, чтобы ехать, а не для того, чтобы рулить.
+1
По поводу заголовка, кажысь правильнее было бы написать «Как безопасно разрушить объект» и другие мысли.
0
typo: не правильный деструктор -> неправильный деструктор
0
НЛО прилетело и опубликовало эту надпись здесь
По поводу shared_ptr — это не так в случае полиморфизма, поскольку тогда он объявляется для абстрактного класса, а ссылается на конкретные объекты, и остаются все те же проблемы, что и с обычным указателем.
0
A *get_a() { return new B; }
...
std::shared_ptr<A> a(get_a());
0
НЛО прилетело и опубликовало эту надпись здесь
Хорошая статья — удачное жонглирование псевдологикой. Элементарные и корректные примеры… не относящиеся к теме. Все остальное — метафоры, «не вызывает ни у кого сомнений», «не есть хорошо и может выйти вам боком» и прочие откровения британских ученых.
Единственный корректный аргумент во всей статье — не может использоваться в объединениях. Ну ради объединений не грех отказаться от ООП.
Единственный корректный аргумент во всей статье — не может использоваться в объединениях. Ну ради объединений не грех отказаться от ООП.
+7
у меня от статьи когнитивный диссонанс какой-то, простите.
А может это вброс такой?
А может это вброс такой?
+3
По поводу деструкторов моя мысль проста.
Есть хоть один виртуальный метод — сразу же обзаводись виртуальным деструктором!
Если нет — надо думать, на что класс годится; обычно не нужно.
Есть хоть один виртуальный метод — сразу же обзаводись виртуальным деструктором!
Если нет — надо думать, на что класс годится; обычно не нужно.
+1
НЛО прилетело и опубликовало эту надпись здесь
Есть мнение, что стоит вообще отказаться от выделения и освобождения ресурсов в конструкторахи деструкторах объектов в пользу явных вызовов и специализированных интерфейсов и аллокаторов по причине трудностей, возникающих при обработке исключительных ситуаций.
0
Кстати, не стоит еще забывать и о том, что использование виртуальных методов необходимо для обеспечения ABI (Application Binary Interface) совместимости, при разработки динамических (so/dll) библиотек.
0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Как безопасно разрушить объект. И другие мысли