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

в 15:29, , рубрики: c++, 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_, т.к. в теле блока оно просто замещает это имя из внешней области видимости.

Использованная литература:
» Лямбда-выражения

Автор: woodser

Источник

Поделиться новостью

* - обязательные к заполнению поля