dxp 56 1 июля Опубликовано 1 июля · Жалоба Смысл POD типов в том, что их можно копировать побитово, и это переносимо. Это свойство хорошо подходит для задач сериализации (передачи по каналам связи, записи на носители и т.п.). И все ограничения, которые там предъявляются, вполне естественны, ничего искусственно выдуманного там нет. Как только в класс добавляется что-то типа конструктора, определённого пользователем, виртуальных функций и т.п. -- всё то, что делает класс похожим на настоящий тип, определяемый пользователем, это ломает POD'овость. И это опять не прихоть разработчиков языка, а суровая реальность. Например, класс, как тип определяемый пользователем, предусматривает только какой-то вполне определённый набор способов создания нового класса. Если же позволить объект класса копировать с помощью memcpy, то тут возникает ещё один нелегальный способ создавать новые объекты в обход правил. Это дыра в безопасности. Поэтому такое и запрещено. Всё это возникает не из-за того, что авторы языка такие злыдни, хотящие сделать работу простых программистов тяжелее, а из-за того, что жизнь вот такая непростая в своих глубинных проявлениях. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Forger 22 1 июля Опубликовано 1 июля · Жалоба 2 hours ago, Arlleex said: Но шаг влево - и бабах. есть у вас пример, где в поток данных некого порта вместо структуры (т.е. нет никаких методов, только поля с данными) нужно отправить именно класс? в чем сакральный смысл такого "действа"? а то получается какая-то сферическая задача в вакууме ) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 172 1 июля Опубликовано 1 июля · Жалоба 22 минуты назад, Forger сказал: а зачем в протоколе при передаче данных отправлять класс вместо структуры (с соотв прагмой), небось еще и с виртуальными методами или чем-то? есть у вас пример, где вот именно нужно класс отправить в поток данных вместо структуры (т.е. нет никаких методов, только поля с данными)? а то получается какая-то сферическая задача в вакууме ) Нет, не сферическая. Задача банальная: я даже никуда не передаю ничего (пока что). Я хочу создать массив из u32 (считай, очередь), которые будут по-разному интерпретироваться в зависимости от битового поля в этом самом u32. В этот u32 я упаковываю различные форматки и снабжаю полем-ключом. Тред-разгрeбатор в рантайме читает очередной u32, читает в нем поле-ключ и определяет как ему интерпретировать остальные биты в этом слове. Я хотел облагородить все это классами, чтобы можно было на уровне API визуально удобно и лаконично работать с объектами. Например, определить конструкторы, чтобы при создании объекта во внутренний этот самый u32 сразу "ложилось" нужное значение без всяких промежуточных вызовов сеттеров. И все - с этого начинаются проблемы: конструктор определил - потерял POD-овость. Ну и опять же. А почему бы не передавать по каналу связи объекты классов? Это ведь очень удобно. Ты написал библиотеку - дал ее человеку и сказал - у себя на компе вставляешь мои исходники и дергаешь у предоставленных классов такие-то такие-то методы. И ему писать ничего не надо - и тебе больший контроль над вашим сопрягаемым кодом. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Forger 22 1 июля Опубликовано 1 июля · Жалоба 41 minutes ago, Arlleex said: А почему бы не передавать по каналу связи объекты классов? Обычно такие дела - передача данных, довольно низкоуровневые, и реализуются в целях безопасности как можно проще и понятнее хотя бы самому проггеру. Кмк следует просто начать создавать протокольную часть этого проекта сверху вниз (а не снизу вверх как многие), это позволит правильно сформировать интерфейс и уйти от запутанных моделей. У меня как раз так и сделано - простые протокольные дела спрятаны внутри классов, порой очень глубоко, а сам протокол - это некая самодостаточная единица со своими потоками/задачами, средствами синхронизации (очереди семафоры ..). В такой модели построения я не передаю никакие классы целиком, у меня в таких классах среди полей находятся экземпляры портов (в смысле порты типа CAN, UART, ETH и т.д), разумеется в приватной секции. А наружу такие гипер-классы (у меня "модули") общаются исключительно через делегаты, обычно их совсем немного. Модуль - это некий синглтон (почти). При создании нового проекта я просто копирую нужные мне модули из других проектов (причем разных), чуть правлю под задачи нового и он сразу начинает работать как мне нужно. Ведь именно ради этого создаются такие монструозные модели? Для чего же еще )) Поэтому я действительно не понимаю, зачем так усложнять себе жизнь передачей целых классов куда то наружу, ведь там их обратно надо принять и правильно разобрать. В такой схеме конечно немудрено накосячить )) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 172 1 июля Опубликовано 1 июля · Жалоба 1 час назад, Forger сказал: Обычно такие дела - передача данных, довольно низкоуровневые, и реализуются в целях безопасности как можно проще и понятнее хотя бы самому проггеру. У меня как раз так и сделано - простые протокольные дела спрятаны внутри классов, порой очень глубоко... Как-то не вяжется) С одной стороны надо упрощать (я читаю как "реализовывать без всяких классов, в Си-стиле"), с другой стороны - прятать в классы и использовать ООП-модель построения программы. Я поясню на примере. Постараюсь совсем простой. Есть плата с микроконтроллером, есть компьютер. Я пишу код для МК, попутно разрабатывая протокол. Чел, который пишет приложение на ПК говорит - "ну ты это, протокол состряпай, а я его поддержу у себя". Окей. Допустим, все мои параметры, которые я отправляю на комп, умещаются в одно u32 слово. Ну не все сразу, а раздельно - как будто union-ом закодированы. Это слово поделено на битовые поля. Одно из полей - обязательное, и находится в одном месте для разных по смыслу объектов u32. Я сразу заготовлю шаблоны всех видов этого самого u32 (что в нем и где хранится). Разумеется, это будут классы. Я прячу u32 внутрь базового класса и конечно же определяю метод извлечения (чтения) типа сообщения MessageID, чтобы как и мне, так и программисту ПК-шной части не делать одинаковую работу по написанию портянки извлечения битовых полей из rawDataFrame enum MessageID { TEMPERATURE, VOLTAGE, CURRENT }; class MessageRaw { public: MessageRaw(MessageID id) : rawDataFrame(/* тут выражение упаковки id в нужное поле по маске ID_MASK*/) {} MessageID getMessageID() { return ... ; // тут каким-то образом извлекаю битовое поле, где хранится MessageID в rawDataFrame } protected: u32 rawDataFrame; enum { ID_MASK = 0xFu }; }; Как видно, базовый класс уже "оброс" вспомогательным методом. Вот "конкретные" классы (на примере одного, который заполняет температуру) template<MessageID> class Message; // фрейм, который кодирует данные о температуре template<> class Message<TEMPERATURE> : public MessageRaw { public: Message(u32 temp) : MessageRaw(TEMPERATURE) { // тут заполняем соответствующее битовое поле TEMPERATURE_MASK значением temp } u32 getTemperature() { return ...; // извлекаем из rawData базового класса битовое поле TEMPERATURE_MASK } private: enum { TEMPERATURE_MASK = 0x00FFFFF0u // младшие 4 бита всегда отведены под MessageID }; }; У классов с MessageID == VOLTAGE или CURRENT битовые поля могут находиться в других местах и, разумеется, пересекаться. Этакий union. Видно, что конкретный Message<TEMPERATURE> уже оброс, как минимум, методом извлечения кодированной температуры (скорее, полезно для программиста ПК-шной части, кому я это хозяйство отправлять буду). Для своей же части (микроконтроллерной) для удобства я еще определяю конструктор, чтобы можно было написать u32 temp = getTemperature(); Message<TEMPERATURE> msg(temp); // прикинь удобно как, в msg уже автоматически (ОДНОЙ СТРОЧКОЙ) записалась температура куда надо Ага. Круто. Еще круто, что при создании msg сначала вызовется конструктор базового класса, который сразу заполнит MessageID! И теперь этим готовым классом могу пользоваться и я, и другой разраб. Но есть одно западло. Как только я во всю эту красосту привнес конструкторы, я потерял POD-овость объектов. А значит реализовать функции приема/передачи в программные очереди (и оттуда в интерфейс) надежно я не могу. Потому что слетает гарантия одинакового представления layout-ов классов у меня и у другого разраба (у меня микроконтроллер и компилятор Clang, у разраба - хрен пойми что, мне возможно даже не очень известное). Вот и приехали. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Forger 22 1 июля Опубликовано 1 июля · Жалоба 1 hour ago, Arlleex said: С одной стороны надо упрощать (я читаю как "реализовывать без всяких классов, в Си-стиле"), с другой стороны - прятать в классы и использовать ООП-модель построения программы. Для меня лично уход в голый си - это как раз усложнение, ООП тут значительно упрощает структуру проекта. Но это для меня. Поскольку память головы не обладает феноминальными способностями хранить одновременно внутри себя сотни функций и переменных, поэтому нужно обязательное структирирование и выстраивание иерархии в проекте ) 1 hour ago, Arlleex said: Но есть одно западло. Как только я во всю эту красосту привнес конструкторы, я потерял POD-овость объектов. значит напрашивается некий "низкоуровеный" кусок кода, который будет одинаково собираться на любом камне, по крайней мере, при разборе содержимого структуры побайтно, по-другому вряд ли получится одновременно и на дуде играть и в бубен стучать ) например, я бы добавил некий метод (может виртуальный), который разбирает свой собственный же класс или что там нужно, и передает это все добро в байт например некую ориентированную очередь, потом передает наверх, что "мол сырые данные готовы, забирайте". Или отправляет сам, если этому хитрому классу передали доступ к функционалу работы с портом, тут по-разному. С приемом - аналогично, но только наоборот. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 172 1 июля Опубликовано 1 июля · Жалоба Свой вопрос решил следующим образом: описал явный конструктор по-умолчанию в классе-наследнике Descriptor() = default. При этом теперь определение любых пользовательских конструкторов другого вида (с параметрами) не отбрасывает POD-овость класса. И самая хорошая новость, это то, что в <type_traits> есть замечательный шаблонный класс std::is_standard_layout и более подходящий мне std::is_pod<>, который я могу использовать в static_assert()! Сейчас все мои наследуемые типы, как и базовый класс - POD. Ура! Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 15 2 июля Опубликовано 2 июля · Жалоба 11 часов назад, Arlleex сказал: Но есть одно западло. Как только я во всю эту красоту привнес конструкторы, я потерял POD-овость объектов. А значит реализовать функции приема/передачи в программные очереди (и оттуда в интерфейс) надежно я не могу. Можно просто добавить в базовый класс функцию getU32(), и передавать по протоколу этот u32. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 172 2 июля Опубликовано 2 июля · Жалоба 6 минут назад, AHTOXA сказал: Можно просто добавить в базовый класс функцию getU32(), и передавать по протоколу этот u32. Да, так и будет)) Я просто хотел отразить саму неизбежность в обрастании классов всякими плюсовыми прибамбасами даже в низкоуровневом коде. Потому что, ИМХО, терять все те прелести C++ - равносильно покупке нового авто, а продолжать кататься на старом ведре. Я за подход в стиле "нужно чутка подумать над низкоуровневыми классами, чтобы их поведение было максимально предсказуемым при реализации в разных компиляторах и по занимаемым ресурсам". 1 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 172 3 июля Опубликовано 3 июля · Жалоба Как бы разрешить такой ambigous #include <stm32f4xx.h> namespace Hardware { enum Pin { RESET, ENABLE }; } int main() { using namespace Hardware; ... = RESET; // ambigous: RESET из enum в <stm32f4xx.h> или из Hardware::Pin? } Ну, я понимаю, что ADL работает так, что using namespace Hardware вкидывает имена из этого неймспейса в ближайшее объемлющее пространство имен. Выглядит, конечно, костыльно. Типа, "визуально" смотрится как директива "смотри в первую очередь в Hardware", а на самом деле все имена из Hardware неявно оказываются в глобальном пространстве, где "случайно" уже имеется такое имя у энумератора в заголовочном файле stm32f4xx.h. Ну да ладно - пусть оно так. Но можно ли именно задекларировать использование Hardware? Постоянно писать Pin::RESET как-то не очень. Декларировать все элементы перечисления - using Pin::RESET и т.д. - тоже не очень. Потому что как минимум это не надежно, если вдруг в Pin окажется не 2 константы, а три-пять десятков, и можно будет прозевать случайно затесавшийся уже определенный где-то в глобальном пространстве символ (то, что он уже есть, и программа скомпилится). Есть ли что-то похожее на using namespace Hardware, такое же в одну строчку, чтобы имена брались только из этого нэймспейса? using Pin как-то не работает. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Forger 22 3 июля Опубликовано 3 июля · Жалоба 1 hour ago, Arlleex said: using namespace Hardware; у себя в библиотеке под разное железо я вообще отказался от такой записи, а просто явно указываю экземпляр какого пина мне нужен и можно сразу в каком режиме, например: class DevicePower { public: void on() { pin.setToHigh(); } void off() { pin.setToLow(); } private: Hardware::DigitalOutputPin<PA3> pin; } devicePower; и никакой "using namespace Hardware" не требуется, нет возможной каши с именами и т.п. )) вообще работу с железом стараюсь запрятать в другие пусть даже полуабстрактные классы, так гораздо проще переносить код из проекта в проект, в данном случае если схема управления питанием девайса (class DevicePower) изменится, то мне достаточно поправить пару методов этого класса, а не лазить по всему коду разбирая где я там правильно или неправильно дергаю пины )) опережая вопрос на скорость и эффективность работы таких пинов, то вот так делаю: inline void setToHigh() __attribute__((always_inline)) { port->BSRR = setMask; } inline void setToLow() __attribute__((always_inline)) { port->BSRR = resetMask; } после лютой работы оптимизатора получается очень даже быстрый и компактный низкоуровневый код, меня полностью устраивает, даже в полностью программных SPI/I2C портах ) минус - на каждый пин выделяется озу, для хранения ссылки на порт и номер пина, масок, но как правило с этим проблем никогда не было: если пинов у камня много, то у него озу тоже выше крыши )) namespace stm32f0 { class AbstractPin { using Port = GPIO_TypeDef; using PortIndex = uint8_t; public: .... private: Port * port; uint8_t pinIndex; uint32_t setMask; uint32_t resetMask; .... Конечно, Адепты Экономии Каждого Бита Священной ОЗУ проклянут меня за такое лютоо расточительство и вандализм, но мне как бы все равно )) Работает прекрасно, пользоваться - кайф, а остальное - мелочи )) 1 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
EdgeAligned 81 4 июля Опубликовано 4 июля · Жалоба По поводу пинов, я, почитав и перебрав разные варианты, пришел к такому: Pin<GpioA, 4>::High();, который через using CS = Pin<GpioA, 4>; преобразуется в CS::High(); Основная причина выбора такого варианта - получение максимально быстрого кода (при включенной оптимизации). То есть, не удобство для себя, а скорость работы кода. Основа - класс GpioA со статическими шаблонными методами template<uint16_t Value> static void SetPins() {} и тд, и наложенный поверх шаблонный класс template<typename TPort, int PinPos> class Pin со статическими методами static void High() { TPort::template SetPins<1 << PinPos>(); и т.д. } Протестировал - работает компактно и быстро, сворачиваясь в пределе до одной ассемблерной инструкции. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Сергей Борщ 134 4 июля Опубликовано 4 июля · Жалоба 8 часов назад, Arlleex сказал: уже имеется такое имя у энумератора в заголовочном файле stm32f4xx.h В этом файле такого имени нет. Выкиньте кубовые файлы с подобной чушью, оставив вместо них пустые заглушки (чтобы не менять сам stm32f4xx.h и сохранить возможность его обновления без правок). Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 172 4 июля Опубликовано 4 июля · Жалоба 17 минут назад, Сергей Борщ сказал: В этом файле такого имени нет. Выкиньте кубовые файлы с подобной чушью, оставив вместо них пустые заглушки (чтобы не менять сам stm32f4xx.h и сохранить возможность его обновления без правок). https://github.com/fcayci/stm32f4-bare-metal/blob/99256dfe4b8630d707743d8917f326cf600c9eeb/include/stm32f4xx.h#L201 Я никаких кубов не использую. Хедеры беру свежие из крайних DFP от кейла. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
andrew_b 16 4 июля Опубликовано 4 июля · Жалоба Just now, Arlleex said: из крайних DFP Крайноз головного мозга это ржачно. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться