I want to provide a small ready-made solution for those who may need to write a large (or not so) piece of code that the program must execute exactly 1 time; and you may need to place it anywhere (within the limits of reasonable and C ++ syntax rules). If the need for this arises more often than a couple of times in the entire project, it would be good to have at least some more or less working solution for this case.
Immediately to the point
Without arguing for a long time, I immediately post my code that works with the C ++ 11 standard and higher.
#include <iostream> #define DO_ONCE(...) { static bool _do_once_ = ([&](){ __VA_ARGS__ }(), 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 /*
Consider the most important piece of code that does all the work required:
#define DO_ONCE(...) { static bool _do_once_ = ([&](){__VA_ARGS__}(), true); (void)_do_once_; }
It looks not very clear and nice, so I'll sign a little more:
')
#define DO_ONCE(...) \ { \ static bool _do_once_ = ([&] ( ) { __VA_ARGS__ } ( ), true); \ (void)_do_once_; \ }
It works like this - in the code block, a local static variable of the bool type is created, which, using the comma operator, is initialized in two stages:
1. Using the operator "parentheses" is called lambda:
[&] ( ) { __VA_ARGS__ }
which captures by reference all that is in scope and executes the expression that the user passed to the macro DO_ONCE through arguments packed in
__VA_ARGS__ . The preset macro
__VA_ARGS__ will allow you to save all the code inside the block, even if it contains commas (which the preprocessor considers as separators of arguments at the parsing stage).
2. The _do_once_ variable is assigned the value true (the assigned value and the type of the variable itself do not play a role, not counting the size occupied in the program). The record "(void) _do_once_;" need to avoid warning about an unused variable.
At this, the variable initialization is completed and the code will never be executed again, which was what was required.
Cons of the approach:- Requires standard C ++ 11,
- Requires the creation of 1 variable for each DO_ONCE block.
Pros:- Good readability, simple syntax.
- There are no restrictions on the number of operators in the block and on their type (do not try to enter there break and continue, if the loop is outside the body of the block DO_ONCE or the case label, if DO_ONCE is inside the switch).
- The ability to work with variables and functions that are available in the scope of a DO_ONCE call without the additional cost of passing them as arguments.
- There is no risk to get the error of redefining the _do_once_ variable, since in the body of the block, it simply replaces this name from the outer scope.
References:
»
Lambda expressions»
Macro with variable argument lists