Перейти к содержанию
    

Плавный переход C -> C++ под МК

Смысл POD типов в том, что их можно копировать побитово, и это переносимо. Это свойство хорошо подходит для задач сериализации (передачи по каналам связи, записи на носители и т.п.). И все ограничения, которые там предъявляются, вполне естественны, ничего искусственно выдуманного там нет. 

Как только в класс добавляется что-то типа конструктора, определённого пользователем, виртуальных функций и т.п. -- всё то, что делает класс похожим на настоящий тип, определяемый пользователем, это ломает POD'овость. И это опять не прихоть разработчиков языка, а суровая реальность. Например, класс, как тип определяемый пользователем, предусматривает только какой-то вполне определённый набор способов создания нового класса. Если же позволить объект класса копировать с помощью memcpy, то тут возникает ещё один нелегальный способ создавать новые объекты в обход правил. Это дыра в безопасности. Поэтому такое и запрещено.

Всё это возникает не из-за того, что авторы языка такие злыдни, хотящие сделать работу простых программистов тяжелее, а из-за того, что жизнь вот такая непростая в своих глубинных проявлениях.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 hours ago, Arlleex said:

Но шаг влево - и бабах.

есть у вас пример, где в поток данных некого порта вместо структуры (т.е. нет никаких методов, только поля с данными) нужно отправить именно класс? в чем сакральный смысл такого "действа"?

а то получается какая-то сферическая задача в вакууме )

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

22 минуты назад, Forger сказал:

а зачем в протоколе при передаче данных отправлять класс вместо структуры (с соотв прагмой), небось еще и с виртуальными методами или чем-то?

есть у вас пример, где вот именно нужно класс отправить в поток данных вместо структуры (т.е. нет никаких методов, только поля с данными)?

а то получается какая-то сферическая задача в вакууме )

Нет, не сферическая. Задача банальная: я даже никуда не передаю ничего (пока что). Я хочу создать массив из u32 (считай, очередь), которые будут по-разному интерпретироваться в зависимости от битового поля в этом самом u32. В этот u32 я упаковываю различные форматки и снабжаю полем-ключом. Тред-разгрeбатор в рантайме читает очередной u32, читает в нем поле-ключ и определяет как ему интерпретировать остальные биты в этом слове.

Я хотел облагородить все это классами, чтобы можно было на уровне API визуально удобно и лаконично работать с объектами. Например, определить конструкторы, чтобы при создании объекта во внутренний этот самый u32 сразу "ложилось" нужное значение без всяких промежуточных вызовов сеттеров. И все - с этого начинаются проблемы: конструктор определил - потерял POD-овость.

Ну и опять же. А почему бы не передавать по каналу связи объекты классов? Это ведь очень удобно. Ты написал библиотеку - дал ее человеку и сказал - у себя на компе вставляешь мои исходники и дергаешь у предоставленных классов такие-то такие-то методы. И ему писать ничего не надо - и тебе больший контроль над вашим сопрягаемым кодом.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

41 minutes ago, Arlleex said:

А почему бы не передавать по каналу связи объекты классов?

Обычно такие дела - передача данных, довольно низкоуровневые, и реализуются в целях безопасности как можно проще и понятнее хотя бы самому проггеру.

Кмк следует просто начать создавать протокольную часть этого проекта сверху вниз (а не снизу вверх как многие), это позволит правильно сформировать интерфейс и уйти от запутанных моделей.

У меня как раз так и сделано - простые протокольные дела спрятаны внутри классов, порой очень глубоко, а сам протокол - это некая самодостаточная единица со своими потоками/задачами, средствами синхронизации (очереди семафоры ..).

В такой модели построения я не передаю никакие классы целиком, у меня в таких классах среди полей находятся экземпляры портов (в смысле порты типа CAN, UART, ETH и т.д), разумеется в приватной секции.

А наружу такие гипер-классы (у меня "модули") общаются исключительно через делегаты, обычно их совсем немного. Модуль - это некий синглтон (почти).

При создании нового проекта я просто копирую нужные мне модули из других проектов (причем разных), чуть правлю под задачи нового и он сразу начинает работать как мне нужно.

Ведь именно ради этого создаются такие монструозные модели? Для чего же еще ))

 

Поэтому я действительно не понимаю, зачем так усложнять себе жизнь передачей целых классов куда то наружу, ведь там их обратно надо принять и правильно разобрать. В такой схеме конечно немудрено накосячить ))

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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, у разраба - хрен пойми что, мне возможно даже не очень известное). Вот и приехали.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 hour ago, Arlleex said:

С одной стороны надо упрощать (я читаю как "реализовывать без всяких классов, в Си-стиле"), с другой стороны - прятать в классы и использовать ООП-модель построения программы.

Для меня лично уход в голый си - это как раз усложнение, ООП тут значительно упрощает структуру проекта.

Но это для меня. Поскольку память головы не обладает феноминальными способностями хранить одновременно внутри себя сотни функций и переменных, поэтому нужно обязательное структирирование и выстраивание иерархии в проекте )

 

1 hour ago, Arlleex said:

Но есть одно западло. Как только я во всю эту красосту привнес конструкторы, я потерял POD-овость объектов.

значит напрашивается некий "низкоуровеный" кусок кода, который будет одинаково собираться на любом камне, по крайней мере, при разборе содержимого структуры побайтно, по-другому вряд ли получится одновременно и на дуде играть и в бубен стучать )

например, я бы добавил некий метод (может виртуальный), который разбирает свой собственный же класс или что там нужно, и передает это все добро в байт например некую ориентированную очередь, потом передает наверх, что "мол сырые данные готовы, забирайте".

Или отправляет сам, если этому хитрому классу передали доступ к функционалу работы с портом, тут по-разному. С приемом - аналогично, но только наоборот.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Свой вопрос решил следующим образом: описал явный конструктор по-умолчанию в классе-наследнике Descriptor() = default. При этом теперь определение любых пользовательских конструкторов другого вида (с параметрами) не отбрасывает POD-овость класса. И самая хорошая новость, это то, что в <type_traits> есть замечательный шаблонный класс std::is_standard_layout и более подходящий мне std::is_pod<>, который я могу использовать в static_assert()! Сейчас все мои наследуемые типы, как и базовый класс - POD. Ура!

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

11 часов назад, Arlleex сказал:

Но есть одно западло. Как только я во всю эту красоту привнес конструкторы, я потерял POD-овость объектов. А значит реализовать функции приема/передачи в программные очереди (и оттуда в интерфейс) надежно я не могу.

Можно просто добавить в базовый класс функцию getU32(), и передавать по протоколу этот u32.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

6 минут назад, AHTOXA сказал:

Можно просто добавить в базовый класс функцию getU32(), и передавать по протоколу этот u32.

Да, так и будет)) Я просто хотел отразить саму неизбежность в обрастании классов всякими плюсовыми прибамбасами даже в низкоуровневом коде. Потому что, ИМХО, терять все те прелести C++ - равносильно покупке нового авто, а продолжать кататься на старом ведре. Я за подход в стиле "нужно чутка подумать над низкоуровневыми классами, чтобы их поведение было максимально предсказуемым при реализации в разных компиляторах и по занимаемым ресурсам".

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Как бы разрешить такой 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 как-то не работает.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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;
....

Конечно, Адепты Экономии Каждого Бита Священной ОЗУ проклянут меня за такое лютоо расточительство и вандализм,  но мне как бы все равно )) Работает прекрасно, пользоваться - кайф, а остальное - мелочи ))

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...