Jump to content

    

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

Recommended Posts

Forger
13 minutes ago, Arlleex said:

Но разве этот switch-case нагляден?

Да, если весь case в одну строку, то очень наглядно ))

Но повторюсь, это скрыто внутри драйвера, отлажено и забыто. Больше туда лазить смысла нет.

 

Quote

А если там этих возможных вариантов будет сотня-тысяча?

Для таких случаев применю просто тип данных без всяких перечислений и контроль границ с соотв ассертом. Например, драйвер для SerialPort (USART)

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

 

Quote

На самом деле можно приводить миллион примеров, когда без явного cast-а лаконично не обойтись.

Примеры.

1. Функция чтения занятого места в программной очереди (FIFO)

Если очередь построена на базе шаблона, не привязана к конкретным типам данных, то все делается несколько проще :) Опять-таки тоже без cast.

 

Quote

2. Запись адресов каких-то объектов в регистры периферии

Да, с регистрами периферии и другой низкоуровневой "ботвой" по-другому действительно никак :(

Но это тоже все можно скрыть внутри неких драйверов, отладить и забыть. Глаз мозолить не будут )

 

Quote


3. Использование функций с несовместимыми указателями

А тут уже лучше работать со ссылками, а не указателями )

Я редко использую указатели как таковые. А, если применяю, то исключительно локально внутри функций или внутри драйверов.

Цель - максимально ограничить их область видимости, т.к. указатель по мне - потенциально ОПАСНЫЙ объект.

Share this post


Link to post
Share on other sites

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

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

А это как? Позиции чтения/записи все равно придется описывать вполне конкретными типами данных.

Цитата

А тут уже лучше работать со ссылками, а не указателями )

Так ведь в очередь я могу передавать вообще что угодно. "Обобщенную" ссылку (void &) ведь сделать нельзя. И в чем "опасность" указателя? А если мне надо записать в очередь целую кучу каких-то данных, размер которых заранее не известен? Не совсем понятна "безопасность" ссылки в этом отношении.

Share this post


Link to post
Share on other sites

Forger
16 minutes ago, Arlleex said:

А это как? Позиции чтения/записи все равно придется описывать вполне конкретными типами данных.

Наверно мы о разных очередях говорим )) Я имел ввиду вот такую очередь:

template <class Item, uint32_t SIZE>
class Queue
{
public:
	Queue() : head(0), tail(0), freeSize(SIZE) { } 	
  
	bool isPutDone(const Item & item)
	{
...
	}

	bool isGetDone(Item & item)
	{
....
    }
	
	void tryToGet(Item & item) { isGetDone(item); }
...

 

Quote

Так ведь в очередь я могу передавать вообще что угодно. "Обобщенную" ссылку (void &) ведь сделать нельзя.

Нет, конечно! Такая самодеятельно строго запрещена. У меня очередь может быть только очередью объектов ОДНОГО типа, разных - нет. Разные уже можно упаковать внутрь самого объекта, добавив ему соотв. методы.

 

 

 

Quote

И в чем "опасность" указателя?

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

 

 

 

Quote

А если мне надо записать в очередь целую кучу каких-то данных, размер которых заранее не известен?

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

 

 

 

Quote

Не совсем понятна "безопасность" ссылки в этом отношении.

Ну, про "ссылки vs указатели" наверно тут уже нет смысла писать, это довольно популярно уже расписано в сторонней литературе ))

 

Вот небольшая цитата со стековерфлоу:

Quote

Принципиальных отличий два:

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

Отсюда и получаем плюсы и минусы использования того и другого:

  • ссылки лучше использовать когда нежелательно или не планируется изменение связи ссылка → объект;
  • указатель лучше использовать, когда возможны следующие моменты в течении жизни ссылки:
    • ссылка не указывает ни на какой объект;
    • ссылка указаывает на разные объекты в течении своего времени жизни.

 

Share this post


Link to post
Share on other sites

dxp

Про различие static_cast и reinterpret_cast сказали выше - это очень разные "по силе" касты (и последствия от преобразований тоже разные), но С не различает их в своём синтаксисе. А смысл их такого громоздкого синтаксиса именно в том, чтобы они  своим безобразным видом прямо-таки подсвечивали место потенциальной ошибки (там где компилятору велели "умыть руки" и не контролировать работы с типами). Каст на С в коде зачастую нетрудно пропустить, т.к. это обычное выражение с круглыми скобками, коими С/C++ злоупотребляют. А *_cast<...>() кроме того ещё и подсвечивается в программерских редакторах, т.к. являются ключевыми словоми языка.

Share this post


Link to post
Share on other sites

Darth Vader
10 часов назад, Forger сказал:

Принципиальных отличий два:

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

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

char a;
char * const ptr_a = &a; // создание и инициализация константного указателя на char

Здесь указатель ptr_a нельзя оставить неинициализированным при его создании. Также, ему нельзя присвоить адрес иного объекта программы (нельзя изменить) т.к. он константный, т.е. неизменяемый.

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

Edited by Darth Vader

Share this post


Link to post
Share on other sites

Arlleex

Кстати да! Ссылка же должна указывать на объект в памяти (т.е. она не может быть NULL).

У меня, например, есть функции, которые могут принимать NULL как вполне допустимый операнд

s32 cFIFO::read(u8 dst[], u32 len) {
  // вычисление занятого места
  // и следующей позиции nxtRPos
  ...
  if(dst != NULL) {
    // чтение из FIFO в dst[]
  }
  rpos = nxtRPos;
  ...
}

Т.е. вызвав read(NULL, 10) мы просто говорим "прочитать 10 элементов вхолостую".
А вот вызвав read(buf, 10) мы просим реально считать 10 элементов из FIFO в buf.

Ссылка здесь не поможет. В общем, есть достоинства и недостатки и у указателей и у ссылок.

Share this post


Link to post
Share on other sites

Forger
5 minutes ago, Arlleex said:

Т.е. вызвав read(NULL, 10) мы просто говорим "прочитать 10 элементов впустую". 

А какой в этом смысл? Это похоже на некий костыль или я чего-то не понимаю.

 

Share this post


Link to post
Share on other sites

Arlleex
1 минуту назад, Forger сказал:

А какой в этом смысл? Это похоже на некий костыль или я чего-то не понимаю.

Бывает нужно просто скипнуть какое-то количество данных в FIFO, так как они по какой-то причине оказались не нужны для обработки.

Share this post


Link to post
Share on other sites

Forger
2 minutes ago, Arlleex said:

Бывает нужно просто скипнуть какое-то количество данных в FIFO, так как они по какой-то причине оказались не нужны для обработки.

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

s32 cFIFO::skip(u32 len) 

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

Share this post


Link to post
Share on other sites

Arlleex
5 минут назад, Forger сказал:

Для такого необычного случая я бы сделал отдельный метод для класса очереди, который делает именно это.

Дело в том, что код read() и такой функции skip() будет на 99% одинаковый, и к сожалению, если в будущем захочется что-то исправить в одной (например, по причине бага), то нужно будет не забыть исправить этот код и в другой. Я стараюсь избегать такого подхода с дублированием кода (естественно, если накладные расходы крайне незначительны, а один лишний if() в read() как раз этот случай). Я думаю, в данном случае можно попробовать создать inline функцию в классе, которая будет вызывать внутри себя read(NULL, len). Ну либо другой способ: dst в функции read() задать NULL по умолчанию. Тогда вызов read(10) как бы намекает, что нужно прочитать 10 элементов в никуда.

Share this post


Link to post
Share on other sites

Forger
11 minutes ago, Arlleex said:

Дело в том, что код read() и такой функции skip() будет на 99% одинаковый, и к сожалению, если в будущем захочется что-то исправить в одной (например, по причине бага), то нужно будет не забыть исправить этот код и в другой. 

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

Его и вызывайте из public методов, чуть ли прямо не описании класса, чтобы не тащить реализацию в cpp файл. Очень наглядно и решает вашу "проблему", нет никакого дублирования кода. Я бы в итоге именно так сам  и сделал )

 

11 minutes ago, Arlleex said:

Тогда вызов read(10) как бы намекает, что нужно прочитать 10 элементов в никуда.

Это очень плохая практика - давать намеки! 

Я все же - сторонник конкретики, по крайней мере в коде :) Поэтому сделал бы просто метод skip, который внутри вызывал бы ваш "толстый", но уже private метод.

Share this post


Link to post
Share on other sites

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

А смысл их такого громоздкого синтаксиса именно в том, чтобы они  своим безобразным видом...

Угу, вот была же аккуратная функция с небольшим размахом строк... И тут на тебе:crazy::biggrin:

Скрытый текст
cSLCAN::eFlow cSLCAN::parseClosed(u8 buf[], u32 &len) const {
  u32   plen = 0;
  eFlow flow = eFlow::INCOMPLETE;
  if(len >= 2) {
          u8 ack = ERR;
    const u8 ch1 = buf[0],
             ch2 = buf[1];
    if(ch1 == 'O') {
      if(ch2 == CR) {
        if(!can->open())
          ack = OK;
        flow = eFlow::SYNC;
      }
      else flow = eFlow::UNSYNC;
      stx->write(&ack, 1);
      plen = 2;
    } else if(len >= 3) {
      const u8 ch3 = buf[2];
      flow = eFlow::UNSYNC;
      plen = 1;
      if(ch1 == 'S') {
        if(ch3 == CR) {
          s32 br = hexToInt1(ch2);
          if(br >= 0 && !can->setBitRate(static_cast<cCAN::eBitRate>(br + 1)))
            ack = OK;
          flow = eFlow::SYNC;
          plen = 3;
        }
      }
      stx->write(&ack, 1);
    }
  }
  len = plen;
  return flow;
}

Share this post


Link to post
Share on other sites

dxp

И сразу отлично видно потенциально проблемное место, на которое компилятор промолчит, и сразу видно, что к чему кастуется. 

 

Вот это:

if(br >= 0 && !can->setBitRate((cCAN::eBitRate)(br + 1)))

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

Share this post


Link to post
Share on other sites

Arlleex

Приветствую всех.

Итак, вопрос следующий: а какой практический смысл в const-методах класса? Да, мы говорим компилятору, что мы этому методу передаем неявно class_type *const this. Т.е. при поытках изменить данные класса вылезет ошибка. Но тут же мы идем в описание этого класса и удаляем const у метода. Я еще понимаю, когда все методы класса - константные (хотя это сложно представить). Но когда класс - сборная солянка из const- и не-const-методов - какой смысл запрещать изменять данные всем этим const-методам?

Share this post


Link to post
Share on other sites

Arlleex

И все-таки. Есть у меня незакрытый гештальт, который я уже пару месяцев назад пытался решить. Назначенная инициализация в C++.

У меня в Keil-е C++11. Соответственно, при компиляции следующего кода вылазит предупреждение

struct sHwCfg {
  struct sGPIO {
    u32    pclkEnPos;
    GPIO_TypeDef &hw;
    u8    pinNum : 4;
  }gpio[2];
  
  struct sCAN {
    u32   pclkEnPos;
    CAN_TypeDef &hw;
  }can;
}static const HwCfgTbl[] = {
#define INIT(n, rxgpio, rxpin, txgpio, txpin, can)                \
          [static_cast<u32>(cCAN::eHwCfg::n)] = {                 \
            {RCC_AHB1ENR_GPIO##rxgpio##EN, *GPIO##rxgpio, rxpin,  \
             RCC_AHB1ENR_GPIO##txgpio##EN, *GPIO##txgpio, txpin}, \
            {RCC_APB1ENR_CAN##can##EN,     *CAN##can}}
  INIT(CAN1_PA11_PA12, A, 11, A, 12, 1),
  INIT(CAN2_PB12_PB13, B, 12, B, 13, 2)
#undef INIT
};
sources/drivers/can.cpp(49): warning: array designators are a C99 extension [-Wc99-designator]


Понятно, что компилятор не хочет, чтобы я "вручную" указывал элементы массива в списке инициализации. Но мне так удобнее читать код. CAN1_PA11_PA12, CAN2_PB12_PB13 - это enum-ы. Естественно, в этот enum могут добавляться новые значения, причем в любом месте. Соответственно, инициализируемый массив не должен от этого "рассинхронизироваться" с назначаемыми элементами инициализации. Тем более у меня массив этих структур - константный: положения всех элементов известны на этапе компиляции программы - ан-нет, все равно держи warning-и:mda:Я уже готов тупо подавить конкретно этот warning, только вот как, чтобы еще не задавить остальные причастные warning-и?

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.