Самый элегантный способ написать однократное "если"
Начиная с C++ 17, можно написать блок if
который будет выполнен ровно один раз, например так:
#include <iostream>
int main() {
for (unsigned i = 0; i < 10; ++i) {
if (static bool do_once = true; do_once) { // Enter only once
std::cout << "hello one-shot" << std::endl;
// Possibly much more code
do_once = false;
}
}
}
Я знаю, что мог бы переосмыслить это, и есть другие способы решить это, но все же - возможно ли написать это как-то так, так что нет необходимости в do_once = false
в конце?
if (DO_ONCE) {
// Do stuff
}
Я имею в виду вспомогательную функцию do_once()
, содержащую static bool do_once
, но что если я static bool do_once
использовать эту функцию в разных местах? Может ли это быть время и место для #define
? Надеюсь нет.
Ответы
Ответ 1
Используйте std::exchange
:
if (static bool do_once = true; std::exchange(do_once, false))
Вы можете сделать это короче, изменив значение истины:
if (static bool do_once; !std::exchange(do_once, true))
Но если вы часто этим пользуетесь, не думайте, а вместо этого создайте обертку:
struct Once {
bool b = true;
explicit operator bool() { return std::exchange(b, false); }
};
И используйте это как:
if (static Once once; once)
Ссылка на переменную не должна быть вне условия, поэтому имя нам не очень дорого. Черпая вдохновение из других языков, таких как Python, которые придают особый смысл идентификатору _
, мы можем написать:
if (static Once _; _)
Дальнейшие улучшения: воспользуйтесь разделом BSS (@Deduplicator), избегайте записи в память, когда мы уже запустили (@ShadowRanger), и дайте подсказку по прогнозированию ветвления, если вы собираетесь тестировать много раз (например, как в вопросе):
// GCC, Clang, icc only; use [[likely]] in C++20 instead
#define likely(x) __builtin_expect(!!(x), 1)
struct Once {
bool b = false;
explicit operator bool()
{
if (likely(b))
return false;
b = true;
return true;
}
};
Ответ 2
Возможно, это не самое элегантное решение, и вы не видите никакого фактического if
, но стандартная библиотека фактически покрывает этот случай: см. std::call_once
.
#include <mutex>
std::once_flag flag;
for (int i = 0; i < 10; ++i)
std::call_once(flag, [](){ std::puts("once\n"); });
Преимущество здесь в том, что это потокобезопасно.
Ответ 3
C++ имеет встроенный примитив потока управления, который уже состоит из "(до блока; условие; после блока)":
for (static bool b = true; b; b = false)
Или хакер, но короче
for (static bool b; !b; b = !b)
Тем не менее, я думаю, что любой из методов, представленных здесь, следует использовать с осторожностью, поскольку они (пока?) Не очень распространены.
Ответ 4
В С++ 17 вы можете написать
if (static int i; i == 0 && (i = 1)){
чтобы не играть с i
в теле цикла. i
начинаю с 0 (гарантируется стандартом), а выражение после ;
множества i
к 1
в первый раз, оно вычисляется.
Обратите внимание, что в С++ 11 вы можете достичь того же с помощью лямбда-функции
if ([]{static int i; return i == 0 && (i = 1);}()){
что также имеет небольшое преимущество в том, что i
не просочился в тело циклы.
Ответ 5
static bool once = [] {
std::cout << "Hello one-shot\n";
return false;
}();
Это решение является поточно-ориентированным (в отличие от многих других предложений).
Ответ 6
Вы можете поместить одноразовое действие в конструктор статического объекта, который вы создаете вместо условного.
Пример:
#include <iostream>
#include <functional>
struct do_once {
do_once(std::function<void(void)> fun) {
fun();
}
};
int main()
{
for (int i = 0; i < 3; ++i) {
static do_once action([](){ std::cout << "once\n"; });
std::cout << "Hello World\n";
}
}
Или вы действительно можете придерживаться макроса, который может выглядеть примерно так:
#include <iostream>
#define DO_ONCE(exp) \
do { \
static bool used_before = false; \
if (used_before) break; \
used_before = true; \
{ exp; } \
} while(0)
int main()
{
for (int i = 0; i < 3; ++i) {
DO_ONCE(std::cout << "once\n");
std::cout << "Hello World\n";
}
}
Ответ 7
Как сказал @damon, вы можете избежать использования std::exchange
, используя уменьшающееся целое число, но вы должны помнить, что отрицательные значения принимают значение true. Способ использовать это будет:
if (static int n_times = 3; n_times && n_times--)
{
std::cout << "Hello world x3" << std::endl;
}
Перевод этого в @Acorn fancy wrapper будет выглядеть так:
struct n_times {
int n;
n_times(int number) {
n = number;
};
explicit operator bool() {
return n && n--;
};
};
...
if(static n_times _(2); _)
{
std::cout << "Hello world twice" << std::endl;
}
Ответ 8
Хотя использование std::exchange
предложенное @Acorn, является, вероятно, самым идиоматичным способом, операция обмена не обязательно дешева. Хотя, конечно, статическая инициализация гарантированно является поточно-ориентированной (если вы не скажете своему компилятору не делать этого), поэтому любые соображения относительно производительности в любом случае оказываются бесполезными при наличии ключевого слова static
.
Если вас беспокоит микрооптимизация (как это часто делают люди, использующие C++), вы можете также поцарапать bool
и использовать вместо него int
, что позволит вам использовать пост-декремент (или, скорее, инкремент, в отличие от bool
int
не насытит до нуля...)
if(static int do_once = 0; !do_once++)
Раньше у bool
были операторы увеличения/уменьшения, но они давно устарели (C++ 11? Не уверен?) И должны быть полностью удалены в C++ 17. Тем не менее, вы можете просто уменьшить int
, и это, конечно, будет работать как логическое условие.
Бонус: вы можете реализовать do_twice
или do_thrice
аналогично...
Ответ 9
На основании @Bathsheba отличный ответ для этого - просто сделал это еще проще.
В C++ 17
вы можете просто сделать:
if (static int i; !i++) {
cout << "Execute once";
}
(В предыдущих версиях просто объявляйте int i
вне блока. Также работает в C :)).
Простыми словами: вы объявляете i, значение по умолчанию которого равно нулю (0
).
Ноль - ложь, поэтому мы используем оператор восклицательного знака (!
), чтобы отрицать его.
Затем мы учитываем свойство приращения оператора <ID>++
, который сначала обрабатывается (назначается и т.д.), А затем увеличивается.
Следовательно, в этом блоке я буду инициализироваться и иметь значение 0
только один раз, когда блок будет выполнен, а затем значение будет увеличиваться. Мы просто используем оператор !
, чтобы отрицать его.
Ответ 10
Самое простое решение - положить в do_once = false
:
if (static bool do_once = true; do_once) { // enter only once
do_once = false;
// ...
}
Чтобы сделать решение "одним выстрелом", я бы использовал троичный:
// if 'do_once' is true, set it to false, and evaluate as true. Otherwise evaluate as false.
if (static bool do_once = true; (do_once ? (static_cast<void>(do_once = false), true) : false)) {
}
Это не потокобезопасно, но будет, если вы сделаете его static thread_local bool
, и в этом случае он будет вызываться один раз для потока.
Чтобы заставить функцию делать это, вам понадобится уникальная "функция" для каждого оператора if, которая выглядит следующим образом, например, do_once<__COUNTER__>()
. Или вы можете использовать статический экземпляр, который будет уникальным для каждого оператора if:
struct do_once {
constexpr explicit operator bool() noexcept {
if (flag) {
flag = false;
return true;
}
return false;
}
private:
bool flag = true;
};
int f() {
if (static do_once _; _) {
return 1;
}
return 0;
}
Ответ 11
Вот решение, которое не включает в себя дополнительные переменные, использует:
if (static bool do_once = true; do_once && !(do_once = false)) {
std::cout << "hello one-shot" << std::endl;
}
Он просто злоупотребляет работой оператора &&
. Первый раз, когда это называется true && true
, оценивается (потому что do_once
- true
а правая сторона всегда true
). Но поскольку правая сторона устанавливает для do_once
значение false
любой вызов после этого приводит к do_once
false && true
(что приводит к do_once
false
). Так что это if-выражение будет вызываться только один раз!
Ответ 12
Возможно, это не совсем то, что вы просите.
Не помещайте в цикл то, что вы хотели выполнить один раз. Просто оставьте их вне цикла. Кроме того, это будет быстрее, чем проверка некоторого однократного условия в цикле.
#include <iostream>
int main() {
std::cout << "hello one-shot" << std::endl;
// Possibly much more code
for (unsigned i = 0; i < 10; ++i) {
// code that was intended to be in the loop.
}
}