Arlleex 188 19 октября, 2021 Опубликовано 19 октября, 2021 · Жалоба Я просто к чему этот вопрос с наследованиями... У меня есть факультативная задача - написать CAN-UART-преобразователь на STM32. Наши гуру линукса, конечно же, просят SLCAN. Я бы мог взять свой любой проект и адаптировать исходник под новый МК (в силу дефицита обкатанные камни купить не смогли). Там все на Си. Ну и довольно "топорная" работа с входным потоком UART. Захотел перетащить под C++, да чтобы было красиво как в коде, так и быстро в железе (малый оверхед). Фиг с ним с классом SLCAN - я уже понял как я сделаю его реализацию. Чтобы абстрагировать класс парсинга и обработки протокола SLCAN от приема и передачи данных по UART/CAN, последние я реализую совершенно независимо. Еще по разумным соображениям мне нужен FIFO как для передачи по UART, так и для приема. Полностью программная реализация, которая у меня уже есть, мне не нравится, т.к. я хочу использовать DMA на прием и на передачу. Соответственно, почему нельзя сделать просто 2 разных реализации классов FIFO? Типа такого namespace FIFO { class cSoftImpl { // программная реализация ... u32 write(u8 buf[], u32 len); u32 read(u8 buf[], u32 len); ... }; class cDMAImpl { // реализация с DMA-контроллером ... u32 write(u8 buf[], u32 len); u32 read(u8 buf[], u32 len); ... }; } В программе создается объект любого из FIFO и весь исходник, работающий с этим объектом, не меняется. Правда, если не использовать указатели на такие классы... Либо наследовать cDMAImpl от cSoftImpl и переопределить его методы. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
one_eight_seven 6 19 октября, 2021 Опубликовано 19 октября, 2021 (изменено) · Жалоба 13 minutes ago, Arlleex said: В программе создается объект любого из FIFO и весь исходник, работающий с этим объектом, не меняется. Правда, если не использовать указатели на такие классы... Либо наследовать cDMAImpl от cSoftImpl и переопределить его методы. либо создать ABC: class cFifoIf { // реализация с DMA-контроллером ... virtual u32 write(u8 buf[], u32 len) = 0; virtual u32 read(u8 buf[], u32 len) = 0; ... }; И именно его использовать на более высоком уровне. А классы софтовой или DMA реализации - унаследовать от него. Тогда замена интерфейса FIFO не потребует никакого изменения в коде, который использует этот интерфейс. Более того, вы можете закрыть их от дальнейшего наследования квалификатором final. Изменено 19 октября, 2021 пользователем one_eight_seven Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 188 19 октября, 2021 Опубликовано 19 октября, 2021 · Жалоба 11 минут назад, one_eight_seven сказал: ...либо создать ABC: С виртуальными как раз понятно - но так я стал бы делать где-нибудь на ПК-шном приложении. У меня то МК Хочется без динамического полиморфизма (потому что это довольно затратно). Прошу извинить за ламерские хотелки - но я сейчас именно на острие лезвия между качеством исходника и производительностью. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
one_eight_seven 6 19 октября, 2021 Опубликовано 19 октября, 2021 · Жалоба Ну, а чего вы боитесь? Измеряли? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
haker_fox 61 19 октября, 2021 Опубликовано 19 октября, 2021 · Жалоба 3 hours ago, jcxz said: то тогда надо создать такой базовый класс для всех устройств ввода/вывода - UART, USB, SPI и т.п. У меня так и сделано. И есть общий абстрактный класс, в котором определены виртуальные функции open(), close() (эта не реализуется, т.к. во встраиваемой системе мне нет нужды закрывать драйвера), имеется член m_open, который сохраняет результат открытия драйвера. 3 hours ago, jcxz said: и чем оно лучше простого UartWrite(), SpiWrite() если кроме имён методов оно ничего не объединяет. Лучше или хуже - это понятия субъективные, поэтому отвечу за себя только. Мне лучше тем, что древовидная структура классов драйверов позволяет при необходимости добавлять сущности не к конкретным драйверам, например последовательных портов, а в их базовый класс от которого они наследуются. Или даже в общий базовый класс, если это нужно для всех всех имеющихся драйверов. Данная концепция ни раз себя оправдывала, мне так удобно. Если Вам удобно делать по-другому, то, пожалуй, дискуссию можно закрыть) Каждый останется при своём мнение, а возможно и изменит его в будущем. 3 hours ago, jcxz said: Так "обязан" или "может"? По Вашим же постам: Если у вас в одном проекте нужна функция Close() для UART, а вдругом - не нужна, то даже там где она не нужна, всё равно нужно её реализовывать? Или нет? Смотря как объявлена виртуальная функция в базовом классе. Если это pure virtual (не помню русский термин), то обязан. В противном случае - нет. 3 hours ago, jcxz said: Это с чего они "платформонезависимы"??? Вы ведь мой пример выше читали, забыли? Там где размер символа UART = 24 бита. Сможете Вы на STM32 или на LPC такую длину символа установить? А Вы читаете внимательно то, что я пишу? Я, как я помню, написал "в общем случае". Фраза допускает и то, что есть исключения. 3 hours ago, jcxz said: Нафига мне создавать и передавать какую-то структуру с настройками в функцию инита порта, если у меня в данном проекте нужно для UART устанавливать только скорость (остальные все параметры = const). Так я Вас разве заставляю это делать? Мне, вот, нужно. Т.к. тот же графический интерфейс прибора позволяет задавать настройки порта. И где мне их прикажете хранить? В моём случае они как раз доступны и доступные через единый (т.е. для всех платформ) интерфейс. Для Вашего уникального последовательно порта, который, как мне кажется, является чем-то даже и не похожим на УАПП можно создать свой драйвер, даже не наследуя от базового класса. Здесь, я считаю, нужно выбирать, как проектировать. Тупо наследовать и перегружать я, вроде, пока ни кого не призывал) 3 hours ago, jcxz said: Вот именно! А классо-поклонники я вижу просто не замечают этого. Возможно и будут, признаюсь я не все тонкости Си++ знаю, и в листинг по каждому чиху не смотрю. Смотрю, обычно на map-файл и делаю вывод. 3 hours ago, jcxz said: Т.е. - указатель. Будет то, что сказал Arlleex. Ну и что? 3 hours ago, jcxz said: Жесть какая! Теперь ещё для инита порта, вместо простого BL, будет стоять кучка команд LDR с последующей BLX: чтение указателя на таблицу виртуальных методов, чтение указателя на конкретный виртуальный метод (Init()), а затем - BLX. И вся эта кухня - вместо простого BL! (которого может и не быть если компилятор заинлайнит его). Вот код задачи из текущего проекта (личного) Spoiler void CmpSlave::task() { static Drv::Stm32f0x1UsartCmp<> usart; auto result = usart.open(); for (;;) { wdgmngr.alive(); //result = usart.readFrame(OsApi::Task::SLEEP_FOREVER); result = usart.readFrame(); setLedStatus(result); if (result != RetVal::Ok) continue; if (usart.getRxFrame().getAddress() != Ffwk::Api::General::getCmpAddress()) continue; usart.getTxFrame().setAddress(usart.getRxFrame().getAddress()); executeCommand(usart.getRxFrame(), usart.getTxFrame()); result = usart.writeFrame(100); } } У драйвера последовательного порта перегружен метод open(), вот фрагмент class Stm32f0x1UsartCmp : public Stm32f0x1Usart<Num, RxSize, RxPin, RxAF, TxPin, TxAF> { public: Stm32f0x1UsartCmp() {} RetVal open() override { if (this->m_open == RetVal::Ok) return this->m_open; /// TODO automatically choose vector based on template args Nvic::install(USART1_IRQn, irqHandler); setupDirPin(); setDirIn(); this->setNvic(); this->enableClock(); BaseUsart::Settings settings; settings.baudrate = BAUDRATE; this->m_open = this->setSettings(settings); flush(); return this->m_open; } Видно, что драйвер отнаследован. Вот листинг \ In section .text, align 4, keep-with-next 11 void CmpSlave::task() { \ _ZN4Link8CmpSlave4taskEv: (+1) \ 0x0 0xB57C PUSH {R2-R6,LR} \ 0x2 0x.... LDR R0,??DataTable2_5 \ 0x4 0x7800 LDRB R0,[R0, #+0] \ 0x6 0x2800 CMP R0,#+0 \ 0x8 0xD105 BNE ??task_0 12 static Drv::Stm32f0x1UsartCmp<> usart; \ 0xA 0x2001 MOVS R0,#+1 \ 0xC 0x.... LDR R1,??DataTable2_5 \ 0xE 0x7008 STRB R0,[R1, #+0] \ 0x10 0x.... LDR R0,??DataTable2_6 \ 0x12 0x.... 0x.... BL _ZN3Drv17Stm32f0x1UsartCmpILNS_13BaseSerialBus6BusNumE1ELj1ENS_3Pin3PinILc66ELj7EEELNS3_8FunctionE0ENS4_ILc66ELj6EEELS6_0ENS4_ILc66ELj5EEEEC1Ev 13 14 auto result = usart.open(); \ ??task_0: (+1) \ 0x16 0x.... LDR R0,??DataTable2_6 \ 0x18 0x.... 0x.... BL _ZN3Drv17Stm32f0x1UsartCmpILNS_13BaseSerialBus6BusNumE1ELj1ENS_3Pin3PinILc66ELj7EEELNS3_8FunctionE0ENS4_ILc66ELj6EEELS6_0ENS4_ILc66ELj5EEEE4openEv \ 0x1C 0x0005 MOVS R5,R0 15 16 for (;;) { 17 wdgmngr.alive(); \ ??task_1: (+1) \ 0x1E 0x.... LDR R4,??DataTable2_7 Нет BLX'ов. Более того, компилятор зайнлайнил вызов этой функции (остальные не стал проверять). В том же листинге (фрагмент) \ In section .text, align 4 \ __softfp RetVal Drv::Stm32f0x1UsartCmp<>::open() \ _ZN3Drv17Stm32f0x1UsartCmpILNS_13BaseSerialBus6BusNumE1ELj1ENS_3Pin3PinILc66ELj7EEELNS3_8FunctionE0ENS4_ILc66ELj6EEELS6_0ENS4_ILc66ELj5EEEE4openEv: (+1) \ 0x0 0xB5FE PUSH {R1-R7,LR} \ 0x2 0x0004 MOVS R4,R0 \ 0x4 0x88A0 LDRH R0,[R4, #+4] \ 0x6 0x2800 CMP R0,#+0 \ 0x8 0xD101 BNE ??open_4 \ 0xA 0x88A0 LDRH R0,[R4, #+4] \ 0xC 0xE02A B ??open_5 \ ??open_4: (+1) 3 hours ago, jcxz said: А потом удивляемся, почему на казалось бы ещё вчера мощном CPU, сегодня наша программа еле ворочается. Я не помню, чтобы на форуме жаловался на медленную работу ПО,. 3 hours ago, jcxz said: Вот это и есть - оверхед в полный рост! Оверхэд есть всегда. И он не только в количестве машинных инструкций выражается. А, например, в потере времени на форуме)))) 2 hours ago, jcxz said: Таким образом - вместо улучшения читаемости (которое вроде должно давать применение ООП), получили прямо противоположный результат. Стало совершенно нечитаемо. Так в чём же дело? В неправильном инструменте? Или в неправильном его использовании? Вы наверняка видели нечитваемые исходники на Си или Си++ без ООП. Я, вот, видел. Даже сегодня у коллеги, все прелести: магические числа, константы и переменные названы в одном стиле, строки сравниваются не с помощью strcmp, а примерно так if( src[0] == dst[0] && src[1] == dst[1] и т.д. И я не придумываю. Именно так строки он и сравнивает. Конечно, этот код не идёт в наши приборы. Он делает тестовые стенды для производства, и приборов не касается. Тоже читаемость никакая? 1 hour ago, Arlleex said: Либо наследовать cDMAImpl от cSoftImpl и переопределить его методы. Я бы сделал абстрактный класс и оба этих наследовал от него. Ну если Вас не пугают VTABLE, которая может там появится. Если код описать в хидере, то с большой вероятностью он заинлайнится, как у меня в примере выше. 1 hour ago, one_eight_seven said: либо создать ABC: Ах, это и есть ABC. Т.е. класс только с интерфейсом (pure virtual functions). Полегчало мне))) 1 hour ago, Arlleex said: (потому что это довольно затратно). Ну вот смотрите, у меня все все все драйверы имеют этого родителя. Как я понял, это и есть ABC. И функция open() заинлайнилась. Да, инлайнится не 100%, но тут либо шашечки, либо ехать)) А если серьёзно, удобство окупается, ИМХО. Да и в листинг глянуть не проблема. Spoiler namespace Drv { class Base { public: typedef void ( *IrqHandlerPtr_t )(); virtual RetVal open() = 0; virtual RetVal close() = 0; protected: RetVal m_open = RetVal::Failed; private: }; }; Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 188 19 октября, 2021 Опубликовано 19 октября, 2021 · Жалоба @haker_fox, у Вас что, open(), даж будучи виртуальным, встроился? Потому как я не вижу чтения VTable... Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
haker_fox 61 19 октября, 2021 Опубликовано 19 октября, 2021 · Жалоба 42 minutes ago, Arlleex said: Потому как я не вижу чтения VTable... Ну вот и я об том же... Я чуть выше неверно выразился, говоря о том, что метод заинлайнился. Торопился. Допустил опечатку. Конечно же не заинлайнился, а просто попал в этот же листинг. Переход BL на него есть. А так, если я всё корректно проверил, да, встроился. Но у меня драйвер описан полностью в хидере. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
repstosw 18 19 октября, 2021 Опубликовано 19 октября, 2021 · Жалоба Классы ради классов? ООП ради ООП? Если Си устраивает, то зачем переходить на "плюсЫ" ? ПлюсЫ в играх нужны. В МК от них головной боли больше, чем пользы. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Forger 26 20 октября, 2021 Опубликовано 20 октября, 2021 · Жалоба 16 minutes ago, repstosw said: ПлюсЫ в играх нужны. В МК от них головной боли больше, чем пользы. Началось Пожалуй пора добавлять в правила запрет на разведение холиваров в стиле C vs C++, моветон. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
one_eight_seven 6 20 октября, 2021 Опубликовано 20 октября, 2021 · Жалоба 32 minutes ago, repstosw said: ПлюсЫ в играх нужны. В МК от них головной боли больше, чем пользы. Да, в играх и в сетевых приложениях. И одно, и другое - крайне требовательны к производительности. Подумайте об этом. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
haker_fox 61 20 октября, 2021 Опубликовано 20 октября, 2021 · Жалоба 1 hour ago, repstosw said: В МК от них головной боли больше, чем пользы. Применяю плюсы в МК года с 2006. Темпалгин и цитрамон не глотаю. Просто Вы не знаете Си++. Это единственно верный ответ. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
turnon 1 20 октября, 2021 Опубликовано 20 октября, 2021 · Жалоба Почему-то ембедеры осваивающие C++ пытаются сразу применить все его изученные возможности. И выходит то же самое что на С, но с кучей классов и виртуальных методов и разочарование в C++ и обвинение в "оверхеде". Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Forger 26 20 октября, 2021 Опубликовано 20 октября, 2021 · Жалоба 18 minutes ago, turnon said: пытаются сразу применить все его изученные возможности Это в целом нормально, потом научаться использовать инструмент правильно ) А кто так и не научился - поливают плюсы грязью, тут в том числе ) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
jcxz 242 20 октября, 2021 Опубликовано 20 октября, 2021 · Жалоба 20 часов назад, haker_fox сказал: Т.к. тот же графический интерфейс прибора позволяет задавать настройки порта. И где мне их прикажете хранить? Естественно - в единой структуре настроек прибора. Хранить настройки в разных местах - это мне кажется по меньшей мере странным. 20 часов назад, haker_fox сказал: В моём случае они как раз доступны и доступные через единый (т.е. для всех платформ) интерфейс. Для Вашего уникального последовательно порта, который, как мне кажется, является чем-то даже и не похожим на УАПП можно создать свой драйвер, даже не наследуя от базового класса. Здесь, я считаю, нужно выбирать, как проектировать. Тупо наследовать и перегружать я, вроде, пока ни кого не призывал) Возможно и будут, признаюсь я не все тонкости Си++ знаю, и в листинг по каждому чиху не смотрю. Смотрю, обычно на map-файл и делаю вывод. Ну и что? Вот код задачи из текущего проекта (личного) Показать содержимое void CmpSlave::task() { static Drv::Stm32f0x1UsartCmp<> usart; auto result = usart.open(); for (;;) { wdgmngr.alive(); //result = usart.readFrame(OsApi::Task::SLEEP_FOREVER); result = usart.readFrame(); setLedStatus(result); if (result != RetVal::Ok) continue; if (usart.getRxFrame().getAddress() != Ffwk::Api::General::getCmpAddress()) continue; usart.getTxFrame().setAddress(usart.getRxFrame().getAddress()); executeCommand(usart.getRxFrame(), usart.getTxFrame()); result = usart.writeFrame(100); } } Нет BLX'ов. Более того, компилятор зайнлайнил вызов этой функции (остальные не стал проверять). Ну во-первых - я тут ничего не вижу, так как не вижу исходника соответствующего этому листингу. Во-вторых: насколько могу судить по у Вас open() и не виртуальный. А вот интересно было бы взглянуть на вызовы монструозных методов read() и write() которые как раз виртуальные. И вызовы не через указатель на их собственный класс, а через указатель на родительский класс. А то, что у Вас там получилась BL (или даже заинлайнилось), так это видимо потому, что метод Вы вызывали или через оператор "." для объекта дочернего класса или через указатель на дочерний класс. Естественно в этом случае компилятор знает какой именно метод должен быть вызван в этом месте и ставит BL. Покажите вызов виртуального метода через указатель на родительский класс. Вот тогда и посмотрим. 20 часов назад, haker_fox сказал: Ну вот смотрите, у меня все все все драйверы имеют этого родителя. Как я понял, это и есть ABC. И функция open() заинлайнилась. Прежде чем говорить "гоп", приведите корректный пример. Как я описал выше. 20 часов назад, haker_fox сказал: Да, инлайнится не 100%, но тут либо шашечки, либо ехать)) А если серьёзно, удобство окупается, ИМХО. Да и в листинг глянуть не проблема. Показать содержимое namespace Drv { class Base { public: typedef void ( *IrqHandlerPtr_t )(); virtual RetVal open() = 0; virtual RetVal close() = 0; protected: RetVal m_open = RetVal::Failed; private: }; }; Ах, вон они где у вас объявлены виртуальными! В исходном посте этого не было. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
haker_fox 61 20 октября, 2021 Опубликовано 20 октября, 2021 · Жалоба 9 minutes ago, jcxz said: Хранить настройки в разных местах - это мне кажется по меньшей мере странным. Не в разных, а в тех местах, которым эти настройки принадлежат. По-другому мне кажется странным... 11 minutes ago, jcxz said: Покажите вызов виртуального метода через указатель на родительский класс. Вот тогда и посмотрим. Вот добавил код снизу. Spoiler void CmpSlave::task() { static Drv::Stm32f0x1UsartCmp<> usart; auto result = usart.open(); for (;;) { wdgmngr.alive(); //result = usart.readFrame(OsApi::Task::SLEEP_FOREVER); result = usart.readFrame(); setLedStatus(result); if (result != RetVal::Ok) continue; if (usart.getRxFrame().getAddress() != Ffwk::Api::General::getCmpAddress()) continue; usart.getTxFrame().setAddress(usart.getRxFrame().getAddress()); executeCommand(usart.getRxFrame(), usart.getTxFrame()); result = usart.writeFrame(100); // Just for test uint8_t buff[10]; Drv::BaseUsart * pbu = &usart; pbu->write(buff, sizeof buff); } } Вот фрагмент листинга. Spoiler 29 // Just for test 30 uint8_t buff[10]; 31 Drv::BaseUsart * pbu = &usart; \ 0xDC 0x.... LDR R4,??DataTable2_6 32 pbu->write(buff, sizeof buff); \ 0xDE 0x2096 MOVS R0,#+150 \ 0xE0 0x0040 LSLS R0,R0,#+1 \ 0xE2 0x9000 STR R0,[SP, #+0] \ 0xE4 0x2300 MOVS R3,#+0 \ 0xE6 0x220A MOVS R2,#+10 \ 0xE8 0xA901 ADD R1,SP,#+4 \ 0xEA 0x0020 MOVS R0,R4 \ 0xEC 0x6826 LDR R6,[R4, #+0] \ 0xEE 0x68B6 LDR R6,[R6, #+8] \ 0xF0 0x47B0 BLX R6 \ 0xF2 0xE794 B ??task_1 33 } 34 } Ну всё, не применяем Си++ во встраиваемых устройствах) Видны обращения к "втабле"))) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться