Использование макроса препроцессора для обертывания функций?

Использование макроса препроцессора для обертывания функций?
Использование макроса препроцессора для обертывания функций? - bona137 @ Unsplash

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

У меня есть соблазн сделать следующее, но это кажется слишком очевидным. Как это может меня подтолкнуть?

#define WITH_CHIP_SELECTED(...) \
  ChipSelect(true);             \
  __VA_ARGS__                   \
  ChipSelect(false);

// usage:
WITH_CHIP_SELECTED(
  BeginTheBeguine();
  GetOutOfTown();
  BrushUpYourShakespeare();
  DontFencMeIn();
)

Согласно godbolt, C-препроцессор будет расширяться до:

ChipSelect(true);
BeginTheBeguine();
GetOutOfTown();
BrushUpYourShakespeare();
DontFencMeIn();
ChipSelect(false);

Да, я знаю, что longjump и нелокальные выходы могут обойти вызов функции exit, но в данном случае это не проблема. В остальном, это кажется идеальным для того, что я хочу. Может быть, я упускаю что-то фундаментальное?

Работает ли это? Да, скорее всего

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

Лучшим и наиболее удобным решением для управления ресурсами является написание этой части кода на C++, чтобы вы могли использовать идиому RAII или, по крайней мере, использовать шаблоны и лямбды. В C++ есть возможности, которые позволяют избежать распространенных ловушек с макросами.

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

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

WITH_CHIP_SELECTED {
  BeginTheBeguine();
  GetOutOfTown();
  BrushUpYourShakespeare();
  DontFencMeIn();
}

Этого можно достичь, написав цикл for, который выполняется один раз. Примерно так:

ChipSelect(true);
for (bool _with_chip_selected_once = true
    ; _with_chip_selected_once
    ; _with_chip_selected_once = false, ChipSelect(false)
    ) {
  ...
}

Более простой, но менее эргономичной альтернативой было бы ожидать, что внутренняя секция будет определена как отдельная функция, тогда:

WITH_CHIP_SELECTED(do_stuff(context));

Который я бы реализовал с помощью идиоматической обертки do-while-false:

#define WITH_CHIP_SELECTED(call) \
  do { \
    ChipSelect(true); call; ChipSelect(false); \
  } while (0)

Да, это позволит использовать несколько утверждений, но это предназначено для другого стиля использования.


LetsCodeIt, 29 декабря 2022 г., 08:45