У меня есть несколько случаев, когда мне нужно вызвать функцию "вход", сделать кучу вещей, а затем вызвать функцию "выход". (В моем случае "вход" - это включение линии выбора микросхемы, а "выход" - ее отключение).
У меня есть соблазн сделать следующее, но это кажется слишком очевидным. Как это может меня подтолкнуть?
#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)
Да, это позволит использовать несколько утверждений, но это предназначено для другого стиля использования.