ワンショットの「if」を書く最もエレガントな方法


136

C ++ 17以降ifでは、次のように1回だけ実行されるブロックを記述できます。

#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ませんか?私は望みません。


52
なぜif (i == 0)ですか?それは十分に明確です。
SilvanoCerza

26
@SilvanoCerzaそれがポイントではないので。このifブロックは、通常のループではなく、複数回実行される関数のどこかにある可能性があります
nada

8
おそらくstd::call_onceオプションです(スレッド化に使用されますが、それでも機能します)。
fdan

25
あなたの例は、あなたが私たちに示していないあなたの現実の問題の悪い反射かもしれません、しかし、ループからコールワンス関数を単に持ち上げないのはなぜですか?
rubenvb

14
if条件で初期化された変数がである可能性があることは、私には起こりませんでしたstatic。それは賢いです。
HolyBlackCat

回答:


143

使用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;
    }
};

32
私は、マクロはC ++での憎悪の多くを得る知っているが、これはちょうどそういまいましいきれいに見えます:#define ONLY_ONCE if (static bool DO_ONCE_ = true; std::exchange(DO_ONCE_, false))として使用するには:ONLY_ONCE { foo(); }
Fibbles

5
つまり、「1回」を3回書いた場合、ifステートメントで3回以上使用することは価値がありますimo
Alan

13
この名前_は、多くのソフトウェアで翻訳可能な文字列をマークするために使用されます。面白いことが起こることを期待してください。
Simon Richter

1
選択できる場合は、静的状態の初期値をすべてビット0にすることをお勧めします。ほとんどの実行可能形式には、すべてゼロの領域の長さが含まれています。
重複排除

7
_変数に使用すると非Pythonicになります。あなたは使用していない_だけで、あなたが店の値に、後で参照される変数のために持っている変数を提供することができますが、値は必要ありません。これは通常、一部の値のみが必要な場合の解凍に使用されます。(他のユースケースもありますが、使い捨てのバリューケースとはかなり異なります。)
jpmc26

91

多分最もエレガントなソリューションではなく、実際のは表示されません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
そのような状況ではstd :: call_onceを認識していませんでした。しかし、このソリューションでは、std :: call_onceを使用するすべての場所でstd :: once_flagを宣言する必要がありますね。

11
これは機能しますが、単純なifソリューションではなく、マルチスレッドアプリケーションを対象としています。内部同期を使用するため、単純なものではやりすぎです。彼はスレッドセーフなソリューションを求めていませんでした。
マイケルチョルダキス

9
@MichaelChourdakis同意します、それはやり過ぎです。ただし、読みにくいifトリケリーの背後に何かを隠すのではなく、何をしているのか(「このコードを1回実行する」)可能性について知っておく価値はあります。
lubgr

17
ええと、call_once私に会うということは、一度何かを呼びたいということです。クレイジー、私は知っている。
バリー

3
@SergeyAは内部同期を使用しているためです。これは、要求されたもの以外のことをやや慣用的に行う方法です。
Orbitのライトネスレース

52

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

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

またはハッカーですが、より短いです:

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

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


1
私は最初のオプションが好きです(ただし、ここの多くのバリアントと同様に、スレッドセーフではないので注意してください)。2番目のオプションはチルを与えます(読みにくく、2つのスレッドだけで何度でも実行できます... b == falseThread 1評価!bしてforループに入る、Thread 2評価!bしてforループに入る、Thread 1その処理を行ってforループから出て、ie に設定b == falseします... その処理を行ってforループを終了し、ie に設定して、プロセス全体を無期限に繰り返すことができるようにします)!bb = trueThread 2b == true!bb = false
CharonX

3
一部のコードが一度だけ実行されることになっている問題に対する最もエレガントな解決策の1つがループであることは皮肉なことです。+1

2
私はを避けb=!bます。見た目はいいですが、実際には値をfalseにしたいので、b=false優先する必要があります。
ヨー '

2
保護されたブロックがローカルに存在しない場合は、再度実行されることに注意してください。それも望ましいかもしれませんが、他のすべてのアプローチとは異なります。
Davis Herring

29

C ++ 17では次のように書くことができます

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

iループ本体での遊びを避けるために。i0で始まり(標準で保証)、最初の評価に;設定さi1た後の式。

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

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

これiは、ループ本体に漏れないという点でもわずかな利点があります。


4
私が言うのは悲しいです-それがCALL_ONCEと呼ばれる#defineに入れられれば、もっと読みやすくなります
nada

9
一方でstatic int i;かもしれないが(私は確信して本当にないんだけど)これらのケースの一つであるiに初期化されることが保証され0、それが使用することを非常に明確だstatic int i = 0;ここ。
カイル・ウィルモン

7
イニシャライザが理解のための良いアイデアであることに私が同意するかどうかにかかわらず
オービットのライトネスレース

5
@Bathsheba Kyleはそうしなかったので、あなたの主張はすでに間違っていることが証明されています。コードを明確にするために2文字追加するのにいくらかかりますか?さあ、あなたは「チーフソフトウェアアーキテクト」です。あなたはこれを知っておくべきです:)
オービットの軽さの

5
);あなたが考える場合は、変数の初期値は明らかに反している、または「何かファンキーが起こっている」ことを示唆している、私はあなたが助けすることができないと思います綴る
軌道上での明度レース

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

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


3
()ラムダ宣言のオプション(空の場合)は知っていますか?
Nonyme

9

条件付きの代わりにインスタンス化する静的オブジェクトのコンストラクターで、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";
    }
}

8

@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

std::exchange@Acornによって提案されているように使用することはおそらく最も慣用的な方法ですが、交換操作は必ずしも安価ではありません。もちろん、静的初期化はスレッドセーフであることが保証されています(コンパイラーに実行しないように指示しない限り)static

あなたは(C ++を使っている人が多いよう)マイクロ最適化を懸念している場合は、あなたにも傷つけることがあるのでbool、使いintあなたはポストデクリメント(というか、使用できるようになる、代わりに増分をとは違って、boolデクリメントintされますないゼロに飽和します...):

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

以前boolはインクリメント/デクリメント演算子がありましたが、それらはずっと前に非推奨になり(C ++ 11?わからない?)、C ++ 17では完全に削除されます。それでも、問題intなくデクリメントできます。もちろん、ブール条件として機能します。

ボーナス:実装することもdo_twicedo_thrice同様に...


私はこれをテストしました、そしてそれは最初を除いて複数回発火します。

@ nada:愚かな私、あなたは正しい...それを修正しました。boolかつては、一度に動作して減少していました。ただし、増分はで正常に機能しintます。オンラインデモを見る:coliru.stacked-crooked.com/a/ee83f677a9c0c85a
デイモン

1
これには、何度も実行される可能性があり、do_onceラップアラウンドして最終的に再び0に(そして何度も何度も)ヒットするという問題があります。

より正確に言うと、これはINT_MAX回実行されるようになります。
nada

ええ、そうですが、その場合ループカウンターラップアラウンドすることを除けば、ほとんど問題になりません。20億(署名されていない場合は40億)の反復を実行する人はほとんどいません。その場合でも、64ビット整数を使用できます。利用可能な最速のコンピューターを使用すると、ラップアラウンドする前に死亡するため、訴訟を起こすことはできません。
デイモン

4

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

ではC++ 17、次のように簡単に実行できます。

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

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

簡単に言うと、デフォルト値のゼロ(0)を取るiを宣言します。ゼロは誤っているため、感嘆符(!)演算子を使用して否定します。次に、<ID>++オペレーターのインクリメントプロパティを考慮します。これは、最初に処理(割り当てなど)され、次にインクリメントされます。

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


1
これが以前に投稿されていたとしたら、それはおそらく今受け入れられている答えでしょう。素晴らしいです、ありがとう!
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.