Однократно выполняющийся блок в произвольном месте кода (C++11 и выше)

от автора

Хочу представить небольшое готовое решение для тех, кому может понадобится написать большой (или не очень) кусок кода, который программа должна выполнить ровно 1 раз; причём поместить его может потребоваться куда угодно (в пределах разумного и правил синтаксиса C++). Если потребность в этом возникает чаще, чем пару раз во всём проекте, хорошо бы иметь на этот случай хоть какое-то более-менее работающее и, по возможности, не очень костыльное решение.

Сразу к делу

Не рассуждая долго, сразу выкладываю свой код, работающий со стандарта C++11 и выше.

#include <iostream>  #define DO_ONCE(body)  { static bool _do_once_ = ([&](){body}(), true); (void)_do_once_; }   void Foo(int val) {     using namespace std;     // Имя _do_once_ никак не конфликтует с переменной в макросе DO_ONCE     static unsigned int _do_once_ = 1;          DO_ONCE     (        cout << "[First call of 'Foo' function]" << endl;     )          cout << "Calls: " << _do_once_++ << ", value: " << val << endl; }  int main(int argc, char** argv) {     using namespace std;          for (auto val : {1, 2, 3})     {         Foo(val);                  DO_ONCE         (             Foo(val);         )              }     system("pause > nul");          return 0; }  /* Результат работы: [First call of 'Foo' function] Calls: 1, value: 1 Calls: 2, value: 1 Calls: 3, value: 2 Calls: 4, value: 3 /* 

Рассмотрим самый важный кусок кода, который выполняет всю требуемую работу:

#define DO_ONCE(body)  { static bool _do_once_ = ([&](){body}(), true); (void)_do_once_; } 

Выглядит не очень понятно и приятно, поэтому распишу чуть подробнее:

#define DO_ONCE(body)  \ {  \     static bool _do_once_ = ([&] ( ) { body } ( ), true);  \     (void)_do_once_;  \ } 

Работает так — в блоке кода создаётся локальная статическая переменная типа bool, которая, с помощью оператора «запятая», инициализируется в два этапа:

1. С помощью оператора «круглые скобки» вызывается лямбда:

[&] ( ) {     body } 

которая захватывает по ссылке всё, что есть в области видимости и выполняет выражение, которое пользователь передал макросу DO_ONCE через аргумент body.

2. Переменной _do_once_ присваивается значение true (присваиваемое значение и тип самой переменной роли не играют, не считая занимаемый в программе размер). Запись "(void)_do_once_;" нужна чтобы избежать warning о неиспользуемой переменной.

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

Минусы подхода:

— Требует стандарт C++11,
— Требует создания 1 переменной на каждый блок DO_ONCE.

Плюсы:

— Хорошая читаемость, простой синтаксис.
— Нет ограничений на кол-во операторов в блоке и на их тип (не пытайтесь вписать туда break и continue, если цикл снаружи тела блока DO_ONCE или метку case, если DO_ONCE внутри switch).
— Возможность работать с переменными и функциями, доступными в области видимости вызова DO_ONCE без дополнительных затрат на передачу их в качестве аргументов.
— Нет риска получить ошибку переопределения переменной _do_once_, т.к. в теле блока оно просто замещает это имя из внешней области видимости.

Использованная литература:
» Лямбда-выражения
ссылка на оригинал статьи https://habrahabr.ru/post/314618/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *