Хочу представить небольшое готовое решение для тех, кому может понадобится написать большой (или не очень) кусок кода, который программа должна выполнить ровно 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/
Добавить комментарий