用最优雅的方式来写一个一次性的“如果”

因为 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的 helper 函数 do_once(),但是如果我想在不同的地方使用同一个函数会怎么样呢?现在是做 #define的时间和地点吗?希望不是。

19530 次浏览

也许不是最优雅的解决方案,您也没有看到任何实际的 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 + + 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没有泄漏到循环主体。

使用 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)

变量不应该在条件之外被引用,所以这个名称并不能给我们带来多少好处。我们可以从其他语言(比如 巨蟒,它赋予了 _标识符特殊的含义)中获得灵感,我们可以这样写:

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

您可以在静态对象的构造函数中包装这个一次性操作,这个静态对象是您实例化的,而不是条件对象。

例如:

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

C + + 确实有一个内置的控制流原语,它已经包含了“(条件; 条件)”:

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

或者更黑,但更短:

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

然而,我认为这里介绍的任何技术都应该谨慎使用,因为它们还没有(还没有?)很常见。

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

此解决方案是线程安全的(与其他许多建议不同)。

尽管按照@Acorn 的建议使用 std::exchange可能是最惯用的方法,但交换操作并不一定便宜。当然,静态初始化肯定是线程安全的(除非您告诉编译器不要这样做) ,所以在 static关键字存在的情况下,任何关于性能的考虑都是徒劳的。

如果你关心微观优化(就像使用 C + + 的人经常做的那样) ,你也可以从头开始使用 bool,改用 int,这样你就可以使用后递减法(或者更确切地说,使用 增量,就像 bool递减一个 int会使 没有饱和到零一样... ...) :

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

过去,bool有递增/递减操作符,但是它们很久以前就被弃用了(C + + 11?不确定?)并将在 C + + 17中完全删除。尽管如此,您还是可以将 int递减得很好,而且它当然可以作为布尔条件工作。

奖励: 您可以实现类似的 do_twicedo_thrice..。

正如@damon 所说,您可以通过使用递减整数来避免使用 std::exchange,但是您必须记住负值解析为真。使用它的方法是:

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

基于@Bathsheba 对这个问题的精彩回答——只是让问题变得更简单了。

C++ 17中,你可以简单地做:

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

(在以前的版本中,只需在代码块之外声明 int i,也可以在 C: 中使用)。

简单来说: 您声明 i,它的默认值为0(0)。 0是假值,因此我们使用叹号(!)运算符来否定它。 然后我们考虑 <ID>++操作符的递增属性,它首先被处理(分配等) ,然后递增。

因此,在这个块中,i 将被初始化,并且值 0只有一次,当块被执行时,值将增加。我们只是使用 !运算符来否定它。