web-dev-qa-db-fra.com

Manière la plus élégante d'écrire un «si» en une fois

Depuis C++ 17, on peut écrire un bloc if qui sera exécuté exactement une fois comme ceci:

#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;
        }

    }
}

Je sais que je pense peut-être trop à cela, et il y a d'autres façons de résoudre cela, mais quand même - est-il possible d'écrire ceci d'une manière ou d'une autre, donc il n'y a pas besoin du do_once = false À la fin?

if (DO_ONCE) {
    // Do stuff
}

Je pense à une fonction d'aide, do_once(), contenant le static bool do_once, Mais que faire si je voulais utiliser cette même fonction à différents endroits? Serait-ce le moment et le lieu d'un #define? J'espère que non.

134
nada

Utilisation std::exchange :

if (static bool do_once = true; std::exchange(do_once, false))

Vous pouvez le raccourcir en inversant la valeur de vérité:

if (static bool do_once; !std::exchange(do_once, true))

Mais si vous l'utilisez souvent, n'hésitez pas et créez un wrapper à la place:

struct Once {
    bool b = true;
    explicit operator bool() { return std::exchange(b, false); }
};

Et utilisez-le comme:

if (static Once once; once)

La variable n'est pas censée être référencée en dehors de la condition, donc le nom ne nous achète pas beaucoup. S'inspirant d'autres langages comme Python qui donnent une signification particulière au _ identifiant, nous pouvons écrire:

if (static Once _; _)

Autres améliorations: profitez de la section BSS (@Deduplicator), évitez l'écriture en mémoire lorsque nous avons déjà exécuté (@ShadowRanger), et donnez un indice de prédiction de branche si vous allez tester plusieurs fois (par exemple, comme dans la question):

// 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;
    }
};
142
Acorn

Ce n'est peut-être pas la solution la plus élégante et vous ne voyez aucun if réel, mais la bibliothèque standard couvre en fait ce cas :, voir std::call_once .

#include <mutex>

std::once_flag flag;

for (int i = 0; i < 10; ++i)
    std::call_once(flag, [](){ std::puts("once\n"); });

L'avantage ici est qu'il s'agit d'un thread-safe.

91
lubgr

C++ a déjà une primitive de flux de contrôle intégrée qui se compose de "(avant-bloc; condition; après-bloc)" déjà:

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

Ou plus piraté, mais plus court:

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

Cependant, je pense que l'une des techniques présentées ici doit être utilisée avec précaution, car elles ne sont pas (encore?) Très courantes.

51
Sebastian Mach

En C++ 17, vous pouvez écrire

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

afin d'éviter de jouer avec i dans le corps de la boucle. i commence par 0 (garanti par la norme), et l'expression après le ; définit i sur 1 la première fois qu'il est évalué.

Notez qu'en C++ 11, vous pourriez obtenir la même chose avec une fonction lambda

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

ce qui présente également un léger avantage en ce que i n'est pas divulgué dans le corps de la boucle.

29
Bathsheba
static bool once = [] {
  std::cout << "Hello one-shot\n";
  return false;
}();

Cette solution est thread-safe (contrairement à la plupart des autres suggestions).

14
Waxrat

Vous pouvez envelopper l'action ponctuelle dans le constructeur d'un objet statique que vous instanciez à la place du conditionnel.

Exemple:

#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";
    }
}

Ou vous pouvez en effet vous en tenir à une macro, qui peut ressembler à ceci:

#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
moooeeeep

Comme l'a dit @damon, vous pouvez éviter d'utiliser std::exchange en utilisant un entier décrémentant, mais vous devez vous rappeler que les valeurs négatives sont résolues à true. La façon de l'utiliser serait:

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

La traduction de cet emballage de fantaisie @ Acorn ressemblerait à ceci:

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
Oreganop

En utilisant std::exchange comme suggéré par @Acorn est probablement le moyen le plus idiomatique, une opération d'échange n'est pas forcément bon marché. Bien que l'initialisation statique soit bien sûr garantie pour les threads (à moins que vous ne disiez à votre compilateur de ne pas le faire), toute considération concernant les performances est de toute façon quelque peu futile en présence du mot clé static.

Si vous êtes préoccupé par la micro-optimisation (comme le font souvent les utilisateurs de C++), vous pouvez aussi bien gratter bool et utiliser int à la place, ce qui vous permettra d'utiliser le post-décrément (ou plutôt , incrément, contrairement à bool décrémenter un intpas saturer à zéro ...):

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

Auparavant, bool avait des opérateurs d'incrémentation/décrémentation, mais ils étaient obsolètes depuis longtemps (C++ 11? Pas sûr?) Et doivent être complètement supprimés en C++ 17. Néanmoins, vous pouvez décrémenter un int très bien, et cela fonctionnera bien sûr comme une condition booléenne.

Bonus: vous pouvez implémenter do_twice ou do_thrice De même...

7
Damon

Basé sur la grande réponse de @ Bathsheba pour cela - vient de le rendre encore plus simple.

Dans C++ 17, vous pouvez simplement faire:

if (static int i; !i++) {
  cout << "Execute once";
}

(Dans les versions précédentes, déclarez simplement int i en dehors du bloc. Fonctionne également en C :)).

En termes simples: vous déclarez i, qui prend la valeur par défaut de zéro (0). Zéro est falsey, donc nous utilisons le point d'exclamation (!) pour l'annuler. On prend alors en compte la propriété incrément du <ID>++, qui est d'abord traité (affecté, etc.) puis incrémenté.

Par conséquent, dans ce bloc, je vais être initialisé et avoir la valeur 0 une seule fois, lorsque le bloc est exécuté, puis la valeur augmente. Nous utilisons simplement le ! opérateur pour l'annuler.

4
Nick Louloudakis