Хочу представить небольшое готовое решение для тех, кому может понадобится написать большой (или не очень) кусок кода, который программа должна выполнить ровно 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_, т.к. в теле блока оно просто замещает это имя из внешней области видимости.
Использованная литература:
» Лямбда-выражения
Автор: woodser