Необходимо создать механизм для выполнения произвольного кода лишь
однажды за всё время выполнения скрипта независимо от того, где этот
кусок расположен. Например, это может понадобиться при отладке, чтобы
где-нибудь в цикле или внутри многократно вызываемой функции (например,
Т.к. код может быть любой, то воспользуемся макросом, код будет примерно таким… и, пожалуй, я сразу покажу полное решение и объясню, как это работает.
Развернём для удобства чтения:
Параметр
В переменной
Так как каждый вызов макроса это подмена по шаблону, то в каждом таком месте будет своя область видимости и своя
После обработки макросов компилятором этот код превращается в:
Такой вызов будет преобразован в такой код:
Ещё пример, но теперь вложенные вызовы неявные:
Вызов внутри метода класса будет сделан только один раз, даже если экземпляров класса несколько.
Это ограничение можно обойти, но код получится уже настолько запутанным, что проще использовать другие методы отладки.
OnCalculate()
) вывести в лог значение контрольной переменной или выражения.Т.к. код может быть любой, то воспользуемся макросом, код будет примерно таким… и, пожалуй, я сразу покажу полное решение и объясню, как это работает.
#define _ONCE(A) { static bool once__ = false; if (!once__) { once__ = true; A; } }
Развернём для удобства чтения:
#define _ONCE(A) \
{ \
static bool once__ = false; \
if (!once__) \
{ \
once__ = true; \
A; \
} \
}
Параметр
A
- это собственно код, который необходимо выполнить. Это может быть почти что угодно, например:_ONCE(Print("rates_total=", rates_total))
_ONCE(show_some_debug_info())
_ONCE(
if (a > b)
Print("a > b");
)
В переменной
once__
хранится признак того, что код A
уже был когда-то выполнен. Этот признак уникален для каждого вызова,
т.к. эта переменная помещена в свою область видимости (внешние фигурные
скобки здесь совсем не для красоты). Для каждой области видимости может
существовать своя статическая переменная, пусть даже с тем же именем,
например такой код вполне рабочий:{
static int a = 1;
}
{
static int a = 2;
}
Print(a); // ошибка, в этой области видимости `a` не существует
Так как каждый вызов макроса это подмена по шаблону, то в каждом таком месте будет своя область видимости и своя
once_
.for (int i = 0; i < 10; i++)
{
_ONCE(Print(i));
_ONCE(Print(i));
}
После обработки макросов компилятором этот код превращается в:
for (int i = 0; i < 10; i++)
{
{
static bool once__ = false;
if (!once__)
{
once__ = true;
Print(i); // A
}
}
{
static bool once__ = false;
if (!once__)
{
once__ = true;
Print(i); // A
}
}
}
// Результат - только эти два нуля в логе:
// 0
// 0
Особенности и ограничения
Вложенный вызов_ONCE(_ONCE(...))
будет работать как без вложения, но компилятор предупредит о скрытии переменной.Такой вызов будет преобразован в такой код:
{
static bool once__ = false;
if (!once__)
{
once__ = true;
{
// на это будет ругаться компилятор, т.к. в этой же области видимости
// уже есть переменная с таким же именем
static bool once__ = false;
if (!once__)
{
once__ = true;
...
}
}
}
}
Ещё пример, но теперь вложенные вызовы неявные:
void test1()
{
_ONCE(Print("test1"); test2();); // once__ 3
}
void test2()
{
_ONCE(Print("test2")); // once__ 4
}
void OnStart()
{
_ONCE(test1(); test1();) // once__ 1 // второй test1() ничего не покажет, т.к. once__ 3 уже отметился
_ONCE(test2(); test2();) // once__ 2 // ничего не покажет, т.к. once__ 4 уже отметился
}
// Результат:
// test1
// test2
Вызов внутри метода класса будет сделан только один раз, даже если экземпляров класса несколько.
class COnceTest
{
public:
void test()
{
for (int i = 0; i < 10; i++)
_ONCE(Print(i));
}
};
void OnStart()
{
COnceTest t1, t2;
t1.test();
t2.test();
}
// Результат:
// 0
Это ограничение можно обойти, но код получится уже настолько запутанным, что проще использовать другие методы отладки.