由于 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
}
我正在考虑一个包含 static bool do_once
的辅助函数 do_once()
,但如果我想在不同的地方使用相同的函数怎么办?这可能是 #define
的时间和地点吗?我希望不是。
if (i == 0)
?这很清楚。
std::call_once
是一个选项(它用于线程,但仍然可以工作)。
if
条件下初始化的变量可能是 static
。这很聪明。
使用 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"); });
这里的优点是这是线程安全的。
call_once
意味着你想调用一次。疯了,我知道。
C++ 确实有一个内置的控制流原语,它已经由“(前块;条件;后块)”组成:
for (static bool b = true; b; b = false)
或者更hacker,但更短:
for (static bool b; !b; b = !b)
但是,我认为这里介绍的任何技术都应该谨慎使用,因为它们(还不是?)很常见。
b == false
:Thread 1
评估 !b
并进入 for 循环,Thread 2
评估 !b
并进入for 循环,Thread 1
完成它的工作并离开 for 循环,将 b == false
设置为 !b
即 b = true
...Thread 2
完成它的工作并离开 for 循环,将 b == true
设置为 !b
即b = false
,允许整个过程无限重复)
b=!b
,这看起来不错,但您实际上希望该值为 false,因此应首选 b=false
。
在 C++17 中,您可以编写
if (static int i; i == 0 && (i = 1)){
为了避免在循环体中玩弄 i
。 i
以 0 开头(由标准保证),;
之后的表达式在第一次计算时将 i
设置为 1
。
请注意,在 C++11 中,您可以使用 lambda 函数实现相同的目的
if ([]{static int i; return i == 0 && (i = 1);}()){
这还具有一点优势,因为 i
不会泄漏到循环体中。
static int i;
可能(我真的不确定)是保证 i
被初始化为 0
的情况之一,但在这里使用 static int i = 0;
会更清楚。
static bool once = [] {
std::cout << "Hello one-shot\n";
return false;
}();
该解决方案是线程安全的(与许多其他建议不同)。
()
在 lambda 声明中是可选的(如果为空)吗?
您可以将一次性操作包装在您实例化的静态对象的构造函数中,以代替条件。
例子:
#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
,这将允许您使用后减量(或者更确切地说,increment< /em>,与 bool
不同,减少 int
将不会饱和为零...):
if(static int do_once = 0; !do_once++)
过去 bool
有递增/递减运算符,但它们很久以前就被弃用了(C++11?不确定?)并且将在 C++17 中完全删除。不过,您可以减少 int
就好了,它当然可以作为布尔条件工作。
奖励:您可以类似地实现 do_twice
或 do_thrice
...
bool
一起使用,并且曾经递减过一次。但增量适用于 int
。查看在线演示:coliru.stacked-crooked.com/a/ee83f677a9c0c85a
do_once
环绕并最终再次达到 0(并且一次又一次......)。
基于@Bathsheba 对此的出色回答 - 让它变得更加简单。
在 C++ 17
中,您可以简单地执行以下操作:
if (static int i; !i++) {
cout << "Execute once";
}
(在以前的版本中,只需在块外声明 int i
。也适用于 C :))。
简单来说:您声明 i,它的默认值为零 (0
)。零是假的,因此我们使用感叹号(!
)运算符来否定它。然后我们考虑 <ID>++
运算符的增量属性,它首先被处理(分配等),然后递增。
因此,在这个块中,i 将被初始化并且只有一次值 0
,当块被执行时,值会增加。我们只需使用 !
运算符来否定它。
不定期副业成功案例分享
#define ONLY_ONCE if (static bool DO_ONCE_ = true; std::exchange(DO_ONCE_, false))
用作:ONLY_ONCE { foo(); }
_
在许多软件中用于标记可翻译的字符串。期待有趣的事情发生。_
将是非 Pythonic。您不要将_
用于稍后将被引用的变量,而只是将值存储在您拥有的位置以提供变量但您不需要该值。当您只需要一些值时,它通常用于解包。 (还有其他用例,但它们与一次性价值案例完全不同。)