web-dev-qa-db-ja.com

ワンショット「if」を記述する最もエレガントな方法

C++ 17以降では、次のように1回だけ実行される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
}
_

_static bool do_once_を含むヘルパー関数do_once()を考えていますが、その同じ関数を異なる場所で使用したい場合はどうすればよいですか?これは_#define_の時間と場所かもしれませんか?しないことを願っています。

134
nada

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

おそらく最もエレガントなソリューションではなく、実際の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"); });

ここでの利点は、これがスレッドセーフであることです。

91
lubgr

C++には、すでに「(before-block; condition; after-block)」で構成される組み込みの制御フロープリミティブがあります。

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

またはハッカー、しかし短い:

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

ただし、ここで紹介する手法はどれも(まだ?)あまり一般的ではないため、注意して使用する必要があると思います。

51
Sebastian Mach

C++ 17で書くことができます

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

ループ本体でiをいじるのを避けるため。 iは0(標準で保証)で始まり、;の後の式は最初に評価されるときにi1に設定します。

C++ 11では、ラムダ関数で同じことを実現できることに注意してください。

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

iがループ本体にリークしないというわずかな利点もあります。

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

このソリューションはスレッドセーフです(他の多くの提案とは異なります)。

14
Waxrat

条件の代わりにインスタンス化する静的オブジェクトのコンストラクターで、1回限りのアクションをラップできます。

例:

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

@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;
}
8
Oreganop

@Acornが示唆するstd::exchangeを使用するのがおそらく最も慣用的な方法ですが、交換操作は必ずしも安価ではありません。もちろん、静的初期化はスレッドセーフであることが保証されています(コンパイラに実行しないように指示しない限り)ので、staticキーワードが存在する場合、パフォーマンスに関する考慮事項は多少無駄になります。

(C++を使用する人がよくそうであるように)マイクロ最適化について懸念がある場合は、boolをスクラッチし、代わりにintを使用することもできます。 、インクリメントboolとは異なり、intをデクリメントするとnotゼロに飽和します...):

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

以前はboolにインクリメント/デクリメント演算子がありましたが、かなり前に廃止され(C++ 11?不明)、C++ 17で完全に削除される予定です。それにもかかわらず、intをデクリメントしても問題ありません。もちろん、ブール条件として機能します。

ボーナス:do_twiceまたはdo_thriceを同様に実装できます...

7
Damon

これに対する@Bathshebaの素晴らしい答えに基づいて-それをさらに簡単にしました。

C++ 17では、次のことが簡単にできます。

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

(以前のバージョンでは、ブロックの外側でint iを宣言するだけです。Cでも機能します:))。

簡単に言えば、デフォルト値ゼロ(0)をとるiを宣言します。ゼロは偽であるため、感嘆符(!)演算子を使用して否定します。次に、<ID>++演算子のincrementプロパティを考慮します。このプロパティは、最初に処理(割り当てなど)されてから増分されます。

したがって、このブロックでは、iが初期化され、ブロックが実行されるときに1回だけ0値を持ち、その後、値が増加します。単純に!演算子を使用して否定します。

4