Самый элегантный способ написать однократное «если» - PullRequest
124 голосов
/ 24 июня 2019

Начиная с 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, но что если я захочу использовать эту же функцию в разных местах? Может ли это быть время и место для #define? Я надеюсь, что нет.

Ответы [ 8 ]

130 голосов
/ 24 июня 2019

Использование 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;
    }
};
89 голосов
/ 24 июня 2019

Возможно, это не самое элегантное решение, и вы не видите ни одного фактического 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"); });

Преимущество здесь в том, что это потокобезопасно.

51 голосов
/ 25 июня 2019

C ++ имеет встроенный примитив потока управления, который уже состоит из "( before-block; условие; after-block )" уже:

for (static bool b = true; b; b = false)

Или хакер, но короче:

for (static bool b; !b; b = !b)

Однако я думаю, что любой из представленных здесь методов следует использовать с осторожностью, поскольку они (пока?) Не очень распространены.

29 голосов
/ 24 июня 2019

В C ++ 17 вы можете написать

if (static int i; i == 0 && (i = 1)){

, чтобы не играть с i в теле цикла.i начинается с 0 (гарантируется стандартом), а выражение после ; устанавливает i в 1 при первой оценке.

Обратите внимание, что в C ++ 11 выможно добиться того же с помощью лямбда-функции

if ([]{static int i; return i == 0 && (i = 1);}()){

, которая также имеет небольшое преимущество в том, что i не проникает в тело цикла.

11 голосов
/ 26 июня 2019
static bool once = [] {
  std::cout << "Hello one-shot\n";
  return false;
}();

Это решение является поточно-ориентированным (в отличие от многих других предложений).

10 голосов
/ 25 июня 2019

Вы можете заключить одноразовое действие в конструктор статического объекта, который вы создаете, вместо условного.

Пример:

#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";
    }
}
9 голосов
/ 27 июня 2019

Как сказал @damon, вы можете избежать использования std::exchange, используя уменьшающееся целое число, но вы должны помнить, что отрицательные значения принимают значение true.Способ использования этого будет выглядеть следующим образом:

if (static int n_times = 3; n_times && n_times--)
{
    std::cout << "Hello world x3" << std::endl;
} 

Перевод этого в необычную обертку @ Acorn будет выглядеть так:

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;
}
7 голосов
/ 26 июня 2019

Хотя использование std::exchange в соответствии с предложением @Acorn, вероятно, является самым идиоматичным способом, операция обмена не обязательно дешева.Хотя, конечно, статическая инициализация гарантированно является поточно-ориентированной (если вы не скажете компилятору не делать этого), поэтому любые соображения по поводу производительности в любом случае оказываются бесполезными при наличии ключевого слова static.

Если вычто касается микрооптимизации (как это часто делают люди, использующие C ++), вы можете также поцарапать bool и использовать вместо него int, что позволит вам использовать пост-декремент (или, скорее, increment в отличие от bool при уменьшении int будет не насыщаться до нуля ...):

if(static int do_once = 0; !do_once++)

Раньше bool имели операторы увеличения / уменьшения,но они давно устарели (C ++ 11 - не уверен?) и должны быть полностью удалены в C ++ 17.Тем не менее, вы можете просто уменьшить int, и это, конечно, будет работать как логическое условие.

Бонус: вы можете реализовать do_twice или do_thrice аналогично ...

...