Comments 59
Не очень понятно почему неиспользуемые функции не могут быть оптимизированы даже если они виртуальные.
gcc "девиртуализирует" функции на -Os/O2
Сейчас девиртуализация в реальном проекте делается компилятором из большой тройки на должном уровне оптимизации, если граф вызовов не очень сложный, в реальном проекте это если создание объекта и вызов метода находятся в той же функции или во вложенной на один-два уровня. Кроме того, невозможно девиртуализировать вызовы объектов, которые приходят в компонент извне.
А чтобы повыбрасывать неиспользуемый код, надо глобально девиртулизовать все виртуальные вызовы, и уже у тех иерархий, у которых это удалось, удалять неиспользуемые методы. На сегодняшний день компиляторы могут делать такое только в тривиальных случаях.
Внутри современные компиляторы типа clang вполне способны на подобные оптимизации для довольно сложных случаев.
Что не отменяет факта, что можно нагородить такого, что компилятор с оптимизатором не смогут ничего сделать.
И никаких виртуальных функций
А почему код должен размножаться? В базовом классе все методы инлайновые, по сути преобразуются в прямой вызов методов наследников. И оптимизацию тут просто компилятор сделает.
Как раз для вашего случая хорошо подойдет. Ну имеется ввиду, если бы сразу так библиотека была бы организована для разделения интерфейсов и реализаций.
Однако создавать базовый класс с чистыми виртуальными функциями только для того, чтобы определить интерфейс для последующих реализаций, не всегда оправдано.
В данном случае оправдано, так как это библиотека и каким-то макаром нужно все равно интерфейс определить. Другое дело, что, как выше сказали можно было это сделать через Странно рекурсивный шаблон.
Но в любом случае, как бы вы узнали какие функции нужно реализовывать, если нет интерфейса? Методом проб и ошибок?
Я не много не ту ссылку вам дал:
Правильная ссылка
И если теперь сделать в реализации так:
using BaseUDP = UDP<EthernetUDP>
//то в самой библиотеке это будет использоваться так
BaseUDP.write(..)
Да, все верно, чтобы подставить разную реализацию, но нужно же знать, реализацию чего, если нет интерфейса, что реализовывать то?
Ну, скажем, мок для юнит-теста сделать проще, если класс полиморфный.
Ну, использование С++ не означает ведь автоматически, что все методы виртуальные. Скорее мораль — "знай свои инструменты".
Например, uVision Keil — одна из сред для разработки для ARM Cortex — со "старым" компилятором armcc, который Arm Compiler 5 более-менее справляется с выкидыванием неиспользуемых виртуальных методов — но при смене компилятора на "новый" — Arm Compiler 6 выясняется, что эта оптимизация пропала.
А новый компилятор — это внезапно clang, в котором такая фишка только-только приехала.
Соответственно, нужно иметь в виду, что подобная проблема может возникнуть при смене компилятора.
Писать на чистом С это уж какое-то слишком радикальное решение.
Микроконтроллеры и задачи разные бывают, подчас на десятки и сотни тысяч строк кода счёт идёт.
Embedded сильно разный везде бывает, у всех своя специфика, для каких-то орехов как раз впору. :) И мегабайт для этого тоже не нужен. Вот из своей практики пример — количество in-house кода подбирается к двадцати тысячам строк (в основном C++, немного C, немного ассемблера в узких местах), если весь библиотечный код посчитать (а библиотек там порядком) то почти полмиллиона строк кода. И это компилируется для машинки с 256к флэша (точнее 192к, остальное отведено на загрузчик с обновлением по воздуху) и 32к оперативной памяти.
Когда проект из крупных, C++ очень сильно помогает. Понятно что если с оглядкой использовать — некоторые фичи накладно тащить на контроллер, а другие — сплошной профит с любой стороны.
Вот — у вас самих проект на 19к LoC, а говорите что не до тысяч строк. :) В моём примере это медицина была, там много всего на борту, тот же offline анализ данных.
Понятно что на C тоже можно делать очень крупные проекты для МК. Просто на C++ это быстрее писать, часто легче читать и поддерживать, и, если знать что обходить стороной, то практически забесплатно.
За меня уже ответили, но все же — проекты и правда бывают разные. Вот сейчас у меня проект, который я с трудом упихиваю в 128 Кб.
Лично я попробовал писать ООП на чистом С и весь исплевался. Я не буду распинаться про всякую шаблонную магию, constexpr и тому подобное (но могу, если хотите) — поскольку не так уж часто всем этим пользуюсь, в основном просто "С с классами".
Мне лично остро не хватает каких-то банальных вещей, скажем, возможности сделать enum, который к int'у не приводится неявно. Я уж молчу про ручной подсчет ссылок (привет, pbuf'ы из lwip)!
В целом это дело вкуса, конечно. Вон, целый Линукс на чистом С написан — и ничего, живут люди, ни о каком С++ и слышать не хотят.
Но вариант "ой, в С++ виртуальные методы не соптимизировать, нафиг этот ваш С++, пойду обратно в С" все же звучит слишком радикально.
либо с вычислениями (ну это мимо микроконтроллеров, видимо)
Интересно, почему. Иногда приходится достаточно плотно считать что-нибудь — ЦОС, например. Или вот я недавно делал на контроллере достаточно обильные расчеты из трехмерной геометрии. Просто вам не попадалось таких задач, видимо (ну или я завышаю сложность того, что попадалось мне :).
А конечные автоматы я нежно люблю, но когда количество состояний превышает пару десятков, пользоваться ими становится довольно тяжело. Ну, лично мне.
У меня сложность с некоторых пор ассоциируется с девайсами, в которых надо учитывать все возможные ситуации — каждый датчик может отказать, каждый интерфейс, причем в разных вариантах — и на все это надо как-то прореагировать, подстелить соломки, просигнализировать. Тут С++ не особо помогает, честно говоря :)
Вообще, если не писать во флешь сотню разных шрифтов, крупные куски изображений и гигантские веб-страницы, то как он может «внезапно» кончиться? Скажем, в МК за 1 бакс 128кБ флеша; я даже не представляю, что нужно туда написать (помимо перечисленного выше), чтобы он кончился. А если взять что-то покруче, то там мегабайт и больше…
P.S. А насчет C vs C++, я тоже долго думал, что кресты — оверхед для микроконтроллеров, пока один товарищ не доказал мне (с представлением ассемблерных листингов), что никакого оверхеда нет, если руки прямые! Зато его код инициализации и работы с устройствами получается чуть ли не втрое короче моего…
STM32 — да, есть где разгуляться
А если руки кривые, то другое дело…
Насчет STM32F103, советую errata на него почитать. Ну и просто RM. Он был первым блином у ST, поэтому там огромное количество косяков (даже настройки GPIO кривые, чего стоит хотя бы невозможность включения подтяжки при работе порта в режиме OUT; ну и других косяков немало).
Как при питании от USB, так и при внешнем питании проблем нет.
FTDI я никогда не пользовался — зачем такую дорогую дуру брать, когда есть дешевые pl2303 и ch340 (последний хорош более простой схемой включения). Но эта фигня ушла в прошлое после полного отказа от МК без встроенного USB!
В этом случае нужно RS485 или CAN использовать.
Если же брать официально, то да — 042 несколько дешевле за счет более слабой периферии. Но, скажем, как USB-CAN — отличное решение!
Когда-то в GCC 3 была -fvtable-gc
как раз для этого, но она была очень глючной и была удалена до лучших времён. А теперь уже -flto
даёт компоновщику куда больше информации чем в своё время сохраняла -fvtable-gc
, так что компилятору ничто не мешает всё девиртуализировать и удалить неиспользуемое.
А вы с -flto
проверили? Или просто в статье не упомянуто? Я ещё раз с испугу перечитал, вдруг, думаю, в первый раз проглядел. :)
Может быть, стоит посмотреть на участок, где создаются объекты производных классов? Это не критика, я верю что вы дотошно всё рассмотрели, так что это мысли вслух; я пробовал, и неиспользованные виртуальные функции GCC с -flto
удалять может, но спугнуть его может что угодно — скажем, если заключить создание объекта производного класса в if то, даже если там выше константа и можно этот if выбросить в ходе оптимизации, то всё равно откажется верить что никаких других конкретных классов из этой иерархии не создаётся.
С другой стороны, полагаться на труднопредсказуемые оптимизации тоже, конечно, нехорошо. Вообще, если получается очень развесистый API с кучей виртуальных функций и потом возникает мысль, что многие из них не используются и реализованы только потому что в базовом классе они чисто виртуальные — то, может быть, этот API слишком много одеяла на себя тянет.
Знакома ситуация, когда место на флэше закончилось, и требуется впихнуть невпихуемое
Иногда не хватает байта. Всего одного.
Виртуальные функции в микроконтроллерах — темная сторона