Jump to content

    
Arlleex

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

Recommended Posts

4 минуты назад, haker_fox сказал:

virtual

Уже будет VTABLE, вызов через таблицу, а это накладные.

Почему нельзя наследовать от базового класса, в котором нет виртуальных методов?

Ну будет тот же write(), только с пустым телом. А в наследнике (в конкретном) уже реализуем как нужно.

Share this post


Link to post
Share on other sites
23 minutes ago, Arlleex said:

Почему нельзя наследовать от базового класса, в котором нет виртуальных методов?

В этом случае будет перегрузка методов и, насколько я понимаю, теряется механизм полиморфизма. А вот дальше я что-то сам не могу разобраться, что это даёт... пойду stack overflow читать)

Share this post


Link to post
Share on other sites
24 minutes ago, Arlleex said:

Ну будет тот же write(), только с пустым телом. А в наследнике (в конкретном) уже реализуем как нужно.

Вы, похоже, рассматриваете класс драйвера в отрыве от его использования.
Класс протокола будет использовать базовый класс драйвера (абстрактный), а не наследующий (реализацию). Это уже в конкретной реализации будет установлен объект наследующего класса, но обращения к  нему будут производиться через указатель базового класса, и write() всё-равно будет вызываться через таблицу виртуальных методов.

2 minutes ago, haker_fox said:

В этом случае будет перегрузка методов и, насколько я понимаю, теряется механизм полиморфизма

А ещё компилируется код, в котором не реализован метод write, ведь базовый класс становится реализованным (concrete) и может быть инстанциирован.

Edited by one_eight_seven

Share this post


Link to post
Share on other sites
54 minutes ago, Forger said:

И где они теперь? )) Небось работают в серьезных индусских организациях

Вы переоцениваете отечественную школу программирования. У нас очень много любителей как создания godlike-объектов, так и нелюбителей абстракций, которые для перемещения лошади двигают не то, что её ногами, а атомами, из которых эта лошадь состоит.

Share this post


Link to post
Share on other sites
44 minutes ago, Arlleex said:

Почему нельзя наследовать от базового класса, в котором нет виртуальных методов?

А вот, нашёл)

Если Вы перегружаете методы базового класса и методы в базовом классе у Вас объявлены без virtual, то может возникнуть такая ситуация. Вы объявляете указатель на базовый класс, и инициалиизируете этот указатель потомком, например обходя массив драйверов-потомков, вызывая метод init(). Если Вы вызовете теперь по указателю некий метод, то сработает метод из базового класса. А это может быть крайне нежелательно, ведь ждёте Вы, скорее всего, совсем другого поведения (что сработает метод класса потомка).

Share this post


Link to post
Share on other sites
1 час назад, Arlleex сказал:

Хотелось бы вот здесь уточнить, что из себя будет представлять сам базовый класс: это абстрактный класс или обычный, наследники которого переопределяют методы базового?

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

Напомню, речь в сообщении, на которое я отвечал, шла о построении некоего УАПП с очень уникальными свойствами, вряд ли на него сверху будет навешана косоль общения с пользователем - вероятнее всего он строится под какой-то конкретный протокол и только под него, а значит полиморфизм ему не нужен и не нужно пытаться подстроить его API под какой-то общий драйвер, можно прсто избежать дублирования кода каких-то общих для всех железных УАПП операций, поместив их в общий базовый класс. Точно так же бессмысленно унифицировать API у УАПП для modbus и УАПП для работы с консолью пользователя.

Share this post


Link to post
Share on other sites
3 часа назад, Сергей Борщ сказал:

Он построен на базе железного УАПП контроллера?

да.

3 часа назад, Сергей Борщ сказал:

Если да - можно отнаследоваться от базового класса, имеющего примитивы работы с железным УАПП - настраивать скорость, четность и т.п и строить свою ветку классов от этого места, пусть даже не все функции базового класса будут использоваться.

А какой тогда смысл? Если только одна функция будет похожая на другие подобные - инициализация, но и та внутри (и по аргументам) - вся другая. Какой тогда смысл в таком наследовании? Когда собственно и ничего не наследуется от родительского.

Класс - он для объединения объектов, имеющих похожие свойства. Чем менее объекты имеют похожих свойств, тем такое объединение бессмысленнее и неэффективнее. Это просто уже - притягивание за уши классов туда, где они совсем не нужны. Только потому что модно/молодёжно.

Считаю что объединять в какие-то логические единицы (в виде класса или ещё как) нужно на более высоком уровне. Например (как у меня) - драйвер отладочного потока. Он передаёт поток как правило через UART (иногда бывают другие каналы) и внутри может вызывать функции установки скорости UART, передачи/приёма символов и т.п. Которые - обычные функции. Т.е. - построение по типу: middleware / IO-уровень.

Share this post


Link to post
Share on other sites
2 часа назад, haker_fox сказал:

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

А какой тогда смысл в таком классе? Если следовать вашей логике, то тогда надо создать такой базовый класс для всех устройств ввода/вывода - UART, USB, SPI и т.п. А какой смысл в таком объединении и чем оно лучше простого UartWrite(), SpiWrite() если кроме имён методов оно ничего не объединяет. Это уже искусственное притягивание классов за уши туда, где они совсем не нужны. И бесполезное загромождение кода бессмысленными сущностями.

 

Цитата

И тогда каждый класс драйвера, отнаследованный от базового, обязан предоставить этот интерфейс.

Так "обязан" или "может"? По Вашим же постам: Если у вас в одном проекте нужна функция Close() для UART, а вдругом - не нужна, то даже там где она не нужна, всё равно нужно её реализовывать? Или нет?

Цитата

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

Это с чего они "платформонезависимы"??? Вы ведь мой пример выше читали, забыли? Там где размер символа UART = 24 бита. Сможете Вы на STM32 или на LPC такую длину символа установить?

 

Цитата

А именно: сама структура, описывающая настройки; и методы, записывающие эти настройки, проверяющие их корректность, возвращающие их по требованию и т.п.

Вот опять оверхед! О котором я говорил. Вы похоже не замечаете просто этого.... Нафига мне создавать и передавать какую-то структуру с настройками в функцию инита порта, если у меня в данном проекте нужно для UART устанавливать только скорость (остальные все параметры = const). Только потому, что в каком-то N-ном проекте мне в UART требовалась не только скорость, но и ещё куча параметров? И потому теперь, во всех проектах вместо передачи бодового значения скорости по значению, мне нужно везде тащить этот интерфейс со структурой!  :shok:

1 час назад, Arlleex сказал:

Уже будет VTABLE, вызов через таблицу, а это накладные.

Вот именно! А классо-поклонники я вижу просто не замечают этого. :biggrin:

Share this post


Link to post
Share on other sites
5 минут назад, jcxz сказал:

А какой тогда смысл? Если только одна функция будет похожая на другие подобные - инициализация

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

У меня же скорость может настраиваться отдельно от инициализации и, соответственно, есть одна для всех УАПП функция настройки скорости в классе variable_speed_uart, которому в качестве базового через параметр шаблона может быть указан generic_uart (который хранит указатель на базовый адрес УАПП и скрывает разницу в именах флагов разных моделей STM32). Аналогично потомкам базовый класс передается в качестве параметра шаблона и можно собирать любую нужную мне комбинацию, например

using serial_port = shutdownable_uart<rx_irq<tx_dma<variable_speed_uart<generic_uart>, 8192>, 8>>

 

Share this post


Link to post
Share on other sites
1 час назад, haker_fox сказал:

В этом случае будет перегрузка методов и, насколько я понимаю, теряется механизм полиморфизма. А вот дальше я что-то сам не могу разобраться, что это даёт... пойду stack overflow читать)

Вы на ровном месте сделали сущестенный оверхед. Совершенно искусственно. Даже не заметив этого.

Это собственно то, с чего я и написал Вам изначальное замечание. С которым Вы всё продолжаете спорить, говоря что "не спорите". И каждый раз, своими же примерами, доказываете мою правоту!  :biggrin: 

 

PS: Да и не будет там перегрузки. Метод же виртуальный. Т.е. - указатель. Будет то, что сказал Arlleex.

 

1 час назад, one_eight_seven сказал:

У нас очень много любителей как создания godlike-объектов, так и нелюбителей абстракций, которые для перемещения лошади двигают не то, что её ногами, а атомами, из которых эта лошадь состоит.

Я смотрю - тут как раз много любителей создания лошади из каждого атома.  :biggrin:

1 час назад, haker_fox сказал:

Если Вы перегружаете методы базового класса и методы в базовом классе у Вас объявлены без virtual

Жесть какая! Теперь ещё для инита порта, вместо простого BL, будет стоять кучка команд LDR с последующей BLX: чтение указателя на таблицу виртуальных методов, чтение указателя на конкретный виртуальный метод (Init()), а затем - BLX. И вся эта кухня - вместо простого BL! (которого может и не быть если компилятор заинлайнит его).

Ну ладно ещё инит порта, но ведь и чтение/запись символов будет работать через такой-же бурелом!!! Каждый символ!  :shok:

А потом удивляемся, почему на казалось бы ещё вчера мощном CPU, сегодня наша программа еле ворочается.  :biggrin:

Вот это и есть - оверхед в полный рост!

Share this post


Link to post
Share on other sites
34 minutes ago, jcxz said:

Класс - он для объединения объектов, имеющих похожие свойства.

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

Share this post


Link to post
Share on other sites
1 час назад, Сергей Борщ сказал:

вероятнее всего он строится под какой-то конкретный протокол и только под него

Конечно. И предназначен для передачи не только собственно кадра данных (единый 24-битный кадр (точнее = 26-битный, если считать со старт- и стоп-битами)), но и точки точной синхронизации (по началу старт-бита) между двумя устройствами.

 

Цитата

, а значит полиморфизм ему не нужен и не нужно пытаться подстроить его API под какой-то общий драйвер

Вот именно об этом я и пишу!

Share this post


Link to post
Share on other sites

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

Spoiler

just-because-you-are-unique-doesnt-mean-

 

Share this post


Link to post
Share on other sites
17 минут назад, one_eight_seven сказал:

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

  Скрыть содержимое

just-because-you-are-unique-doesnt-mean-

 

Вспоминается тут мне один мой бывший коллега. Который несколько лет назад решил освоить ООП. И начал везде где ни попадя создавать классы. Со всякими наследованиями и перегрузками. Я потом заглянул в его код и......! о УЖАС!!! :shok:   Там где раньше были вызовы типа: SetEvent(...), ResetEvent(), SetAlarm(...), ResetAlarm(...), SetState(), ResetState() и т.п. с говорящими названиями, когда глядя на такой код более/менее понимаешь, что там происходит. То теперь там значилось:

некий_объектA_с_ничего_не_значащим_названием.Set(...);

некий_объектB_с_ничего_не_значащим_названием.Set(...);

некий_объектC_с_ничего_не_значащим_названием.Reset(...);

... и так несколько десятков set()/reset()/get() в одной функции, но для разных переменных разного типа.  :dash2:

вобщем - код состоящий из огромного множества Set()/Reset()/Get() и т.п. методов с одинаковыми названиями, но внутри совершенно разных. А чтобы понять - что за Set() в данном месте, нужно каждый раз отматывать исходник к месту объявления каждого такого объекта, чтобы увидеть его тип.

Таким образом - вместо улучшения читаемости (которое вроде должно давать применение ООП), получили прямо противоположный результат. Стало совершенно нечитаемо.

Если раньше можно было просто поиском по имени функции, найти место её определения и посмотреть что она делает, то теперь поиск выдавал тучу одинаковых Set(). :dash2:

Всё как на вашей каринке - вместо столового прибора на столе: с ложкой, вилкой, ножом, тарелкой, получили вот такой ряд из одних вилок!  :dash2:

Share this post


Link to post
Share on other sites
56 минут назад, jcxz сказал:

Вот именно об этом я и пишу!

Нет, вы пишете об обратном:

21 час назад, jcxz сказал:

Вот и попробуйте такое засунуть в "универсальный драйвер".

А "такое" засовывать не нужно. Для этого программисту голова и дана.

Опишу свое решение, может оно натолкнет кого-то на полезную мысль. У меня дерево классов работы с УАПП не имеет виртуальных функций. А вот класс console имеет виртуальные функции write(), read(), is_connected() и уже от console наследуются serial_console, vcp_console и telnet_console, членами которых являются, соответственно, ссылки на serial_port, usb_device::vcp_interface и telnet. И еще один класс, терминал, соотвественно, имеет указатель на console. В результате в устройстве может быть несколько независимых одновременно работающих терминалов на разных интерфейсах.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.