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
_の時間と場所かもしれませんか?しないことを願っています。
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;
}
};
おそらく最もエレガントなソリューションではなく、実際の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"); });
ここでの利点は、これがスレッドセーフであることです。
C++には、すでに「(before-block; condition; after-block)」で構成される組み込みの制御フロープリミティブがあります。
for (static bool b = true; b; b = false)
またはハッカー、しかし短い:
for (static bool b; !b; b = !b)
ただし、ここで紹介する手法はどれも(まだ?)あまり一般的ではないため、注意して使用する必要があると思います。
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
がループ本体にリークしないというわずかな利点もあります。
static bool once = [] {
std::cout << "Hello one-shot\n";
return false;
}();
このソリューションはスレッドセーフです(他の多くの提案とは異なります)。
条件の代わりにインスタンス化する静的オブジェクトのコンストラクターで、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";
}
}
@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;
}
@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
を同様に実装できます...
これに対する@Bathshebaの素晴らしい答えに基づいて-それをさらに簡単にしました。
C++ 17
では、次のことが簡単にできます。
if (static int i; !i++) {
cout << "Execute once";
}
(以前のバージョンでは、ブロックの外側でint i
を宣言するだけです。Cでも機能します:))。
簡単に言えば、デフォルト値ゼロ(0
)をとるiを宣言します。ゼロは偽であるため、感嘆符(!
)演算子を使用して否定します。次に、<ID>++
演算子のincrementプロパティを考慮します。このプロパティは、最初に処理(割り当てなど)されてから増分されます。
したがって、このブロックでは、iが初期化され、ブロックが実行されるときに1回だけ0
値を持ち、その後、値が増加します。単純に!
演算子を使用して否定します。