Как стать автором
Обновить

Комментарии 12

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

Объектов, а не классов. Оно так и называется объектно-ориентированное.

То есть код, в котором в классах только статические методы (соответственно, объекты создавать смысла нет) не относится к ООП?

Нет. Просто объектно-ориентированный код, написанный без классов, не перестаёт быть объектно-ориентированным. И в программе взаимодействуют объекты, а классы — это один из способов описать поведение объектов.

Да, код в котором не создаётся ни одного объекта, а используются только статические методы — это не ООП.

Если объект статический, то будет )))
А вообще, программирование с помощью ключевых слов class, virtual и т.д. не всегда объектно-ориентированное. Либо у вас есть какие-то сущности, инкапсулирующие свое состояние и как-то взаимодействующие, либо нет. Как это технологически сделано вопрос десятый. А иногда, как сказал Роб Пайк, данные — это просто данные.
Стоит ли из-за 1-2% от общего кода разводить всё это?
Делается один раз (HAL/LL/CMSIS) за 2-3 часа и больше не меняется.
Стоит.
Во-первых это по-своему красиво.
Во-вторых отличное применение шаблонам.
А, напоследок, отлично объясняет зачем в кресты добавили constexpr/consteval и отучает использовать шаблоны в более сложных местах :)
Ну если такие аргументы, то согласен. Я имел ввиду реальное программирование.
Возможно, Вы правы, если бы это было целесообразно, промышленность (или даже сами производители) пользовалась бы. Мне всё это дело просто интересно, помогает в более интересном виде изучать нововведения C++, который я преподаю.
Шаблонное программирование — это не просто кодогенерация, а программирование в пространстве типов. Поэтому вместо страшного кода с кучей параметров проще создать новый тип:

struct ExampleTrait {
  constexpr static std::uint32_t  kParam1 = 0;
  constexpr static std::uint32_t  kParam2 = 1;
};

template < class Trait > 
class Bar {
  constexpr static std::uint32_t  kParam1 = Trait::kParam1;
  constexpr static std::uint32_t  kParam2 = Trait::kParam2;

  // Далее идут другие константы и набор статических функций
};


Похожим образом сделана стандартная библиотека с++, хотя мой пример упрощен. Обрати внимание на наличие списка констант — это очень удобная фича.

Далее, необходимо четко разделять сущности. uart — это uart, gpio — это gpio. Мой опыт показывает, что смешивать разные сущности по возможности не стоит, гораздо проще сгруппировать родственную периферию, а потом настроить её одной строкой:

template < class... dev >
  class DeviceSet
  {
    public:
      inline constexpr static void Init()  { ( dev::Init(), ... ); };   
  };

  template < class... IO >
  class IoSet final: public DeviceSet< IO... >
  {
    public:
      inline constexpr static void Set()   { ( IO::Set(), ... ); };
      inline constexpr static void Reset() { ( IO::Reset(), ... ); };
      inline constexpr static void Toggle(){ ( IO::Toggle(), ... ); };
  };


Этот класс довольно туповат и неоптимален, по-крайней мере для stm32 (есть микроконтроллеры, у которых каждый gpio настраивается своей группой регистров, там быстрее не будет)
Оптимизириванный вариант, если таковой возможен, должен использовать маски для группы gpio (пример)

А применение этого добра классическое:
using Leds = mpp::gpio::IoGroup < LedBlue, LedRed, LedOrange, LedGreen >;
bsp::Leds::Init();
bsp::Leds::Toggle();


Uart же более правильно проинициализировать отдельно, включение прерываний также стоит сделать отдельно. Исключения, конечно бывают, Clock Tree и Flash в тех же stm-ках зависят друг от друга, поэтому при натройки ClockTree должен знать о конфиге флеша.

Что касается статей lamerok, в них много интересного, но шаблонная обертка над каждым битом просто лишена смысла. Технически, она может привести к проблемам компиляции при многоуровневой специализации шаблонов.

_Regs::CR1Pack<_Regs::CR1::UE, _Regs::CR1::RE, _Regs::CR1::TE>::Set();
    // Еще какие-то настройки.

UART->CR1 = UART->CR1 | UART_CR1_UE | UART_CR1_RE | UART_CR1_TE; 


Результат эквивалентен, тем более он будет упакован в функцию Init, вызываемую нами в коде. У меня в этом случае четкая ассоциация с блоками unsafe из Rust — я знаю, что здесь может быть ошибка, и найти её легко, тем более библиотечный код интенсивно тестируется.

Гораздо эффективнее сделать и отладить библиотеку для работы с ip-ядрами, тем более генерация обертки над каждым битом не всегда возможна (TI иногда несколько отходит от стандарта CMSIS).

Что касается статей lamerok, в них много интересного, но шаблонная обертка над каждым битом просто лишена смысла.

Поправлю, там обертка не над каждым битом, а над значением поля регистра. Смысл был в том, чтобы запретить ставить значения не описанные спецификацией. В случае с CMSIS можно поставить что угодно, и какой угодно define, который может быть вообще не иметь отношения к регистру. В обертке де будет ошибка компиляции.

Кстати, уход от CMSIS также может сберечь некоторое количество нервных клеток. Как мне кажется, очень красочный пример: в CMSIS от F0 есть макрос
#define RCC_CFGR_PLLMUL
а в F1 это уже
#define RCC_CFGR_PLLMULL
Даже не знаю, почему у них так получилось.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации