如何执行一段代码仅一次?

36

我有一个应用程序,其中包含几个功能。每个功能都可以根据用户输入被多次调用。但是我需要在应用程序启动时仅执行函数中的一小段代码一次。当此后在稍后时间再次调用同一函数时,这个特定的代码段不应该被执行。 代码使用VC++编写,请告诉我处理此问题的最有效方法。


11
能否将其放在程序的开头(或者任何需要的地方)的 main 函数中?或者放在程序的 主循环 之前? - Kiril Kirov
9个回答

50

使用lambda函数的紧凑版:

void foo()
{
    static bool once = [](){
        cout << "once" << endl;
        return true;
    } ();
    cout << "foo" << endl;
}

lambda函数内的代码仅在静态变量初始化为lambda函数返回值时执行一次。只要编译器支持线程安全的静态初始化,它就应该是线程安全的。


1
太好了,我一直认为编译器在初始化静态变量时已经做到了这一点,因此我们应该能够从中受益,这就是正确的答案(而不是使用标志或 run_once())。 - Alexis Wilke
@Bediver 如果你将那个lambda表达式存储在一个自动变量中而不是立即执行它,它就可以被调用多次。或者这是未定义行为吗? - KeyC0de
2
这将显示一个警告'warning: unused variable 'once' [-Wunused-variable]'。使用C++17,您可以使用[[maybe_unused]]来抑制它。 - techolic
2
如果你将这个生成的汇编代码与使用std::call_once时生成的代码进行比较,那么这段代码看起来要好得多!https://godbolt.org/z/s3YPeb - Carlo Wood

43

使用 C++11 -- 使用 std::call_once

#include <mutex>

std::once_flag onceFlag;

{
    ....
    std::call_once ( onceFlag, [ ]{ /* my code body here runs only once */ } );
    ....
}

我之前不知道这个API,谢谢。有几点需要注意:首先确保onceFlag具有全局作用域,不能是线程或函数作用域。其次,我没有找到一种简单地将其压缩为单个表达式的方法。(相比于拥有清晰、标准的API,这并不是一个很大的问题。) - John McFarlane
3
我正在成功地使用这个方法,唯一我要做的是,如果你在函数内部将once_flag声明为一个静态变量,它只会被初始化一次,并且在call_once内部的代码也只会运行一次,但你可以保持所有代码在同一作用域内。 - Andrew97p
你可以在这里阅读更多关于使用的信息:https://en.cppreference.com/w/cpp/thread/call_once - Yushan ZHANG

37

在全局静态对象中使用带有构造函数的对象(这些对象在main之前被调用)?还是只在某个程序中使用

static bool initialized;
if (!initialized) {
   initialized = true;
   // do the initialization part
}

很少情况下会发生这个不够快的情况!


附加说明

在多线程环境中,这可能还不足够:

您可能还需要pthread_once__attribute__函数构造器的GCC。

有了C++11,您可能会想要使用std::call_once

如果您的函数可以从多个线程调用,则可能需要使用<atomic>并声明static volatile std::atomic_bool initialized;(但需小心)。

但是这些可能在您的系统上无法使用;它们在Linux上可用!


1
那也是我的想法,但是在阅读@KirilKirov的评论后,我不得不撞了一下石头。干杯! - Cheers and hth. - Alf
3
这份代码在多线程环境下无法正常工作,但对于大多数情况而言应该还是可以的。 - Kat
1
确保将该布尔值初始化为false!static bool initialized(false);否则,一旦分配内存,您就不知道其中会有什么。 - thayne
2
据我所知,static变量被初始化为全零位,对于bool类型来说是false - Basile Starynkevitch
2
这在C++11中是线程安全的。是的,静态变量会被初始化为零。然而,我仍然认为养成始终明确初始化基本类型的习惯是好的;这样,如果重构后不再隐式初始化,你就不会在以后遇到麻烦了。 - underscore_d
@CarloWood:请提供您自己对那个问题的答案... - Basile Starynkevitch

25

您可以使用局部静态变量:

void foo()
{
     static bool wasExecuted = false;
     if (wasExecuted)
         return;
     wasExecuted = true;

     ...
}

5

你能做这个吗?

需要一个返回布尔值或名为init的某些数据类型的函数

我是这样做的,你需要使用静态布尔值来实现它

bool init()
{
  cout << "Once " <<endl;
  return true||false;// value isn't matter
}

void functionCall()
{
    static bool somebool = init(); // this line get executed once
    cout << "process " <<endl;
}

int main(int argc, char *argv[])
{
    functionCall();
    functionCall();
    functionCall();

    return EXIT_SUCCESS;
}

对于C语言

#include <stdio.h>

void init()
{
    printf("init\n");
}

void process()
{
    static int someint = 0;
    if(someint == 0)
    {
        someint = 1;
        init();
    }
    printf("process\n");
}


int main()
{
    process();
    process();
    process();
    return 0;
}

因为“静态变量只会被初始化一次”,所以它只执行一次吗? - Makesh

5
除了@Basile的答案之外,您还可以使用lambda将静态变量封装如下:
if ([] {
    static bool is_first_time = true;
    auto was_first_time = is_first_time;
    is_first_time = false;
    return was_first_time; } ()) 
{ 
    // do the initialization part
}

这使得转换为通用宏变得简单:
#define FIRST_TIME_HERE ([] { \
    static bool is_first_time = true; \
    auto was_first_time = is_first_time; \
    is_first_time = false; \
    return was_first_time; } ())

这可以放置在任何你想要的地方,使用按需调用

if (FIRST_TIME_HERE) {
    // do the initialization part
}

而且,原子操作可以缩短表达式并使其线程安全:

#include <atomic>
#define FIRST_TIME_HERE ([] { \
    static std::atomic<bool> first_time(true); \
    return first_time.exchange(false); } ())

3

std::call_once()等可能过于严格,如果您不需要完全线程安全的解决方案。

如果不需要,我们可以使用C++17的在if中初始化和std::exchange()来使代码看起来尤为优雅:

#include <utility>

void
do_something_expensive_once()
{
    if ( static auto called = false; !std::exchange(called, true) ) {
        do_something_expensive();
    }
}

如果这是您经常使用的模式,则我们可以通过标签类型进行封装:
#include <iostream>
#include <utility>

template <typename T>
auto
call_once()
{
    static auto called = false;
    return !std::exchange(called, true);
}

void
do_something_expensive()
{
    std::cout << "something expensive\n";
}

void
do_something_expensive_once()
{
    if ( call_once<struct TagForSomethingExpensive>() ) {
        do_something_expensive();
    }
}

auto
main() -> int
{
    for (auto i = 0; i < 5; ++i) {
        do_something_expensive_once();
    }

    return 0;
}

这将仅打印一次something expensive。结果!它还使用在模板参数列表中声明标记struct的能力,以实现最大的简洁性。
或者,您可以在函数地址、唯一整数等上进行模板化。
然后,您还可以将可调用对象传递给call_once()等等。像C ++一样:可能性是无限的!

请那位点踩的朋友给我指点迷津,现在我已经修复了一个简单的拼写错误,这是我能看到的唯一问题。 - underscore_d
我不是那个给你点踩的人,但你的代码是错误的。是的,static auto called以线程安全的方式初始化为false。但是,对std::exchange的并发调用不能保证被序列化。因此,你可能会多次调用do_something_expensive - igor.sol
OP没有指定需要一个线程安全的解决方案,如果不需要也没关系。这并不意味着“我的代码是错误的”,它只意味着它可能对于每个人的具体情况都不足够...无论如何,我已经进行了编辑以澄清。 - underscore_d
如果这不是线程安全的解决方案,那么我就不会使用 std::exchange: if ( static auto called = false; !called ) { do_something_expensive();called = true; } - igor.sol
那有什么关系吗,除了个人风格偏好之外?我喜欢它现在的样子。 - underscore_d

0

尊重 std::call_once(),并注意线程安全的常规警告,这里提供另一个轻量级选项,避免未使用变量的警告,并将我们的标志保持在块范围内:

for (static bool once=true; once; once=false) {
    yourCodeHere();
}

-5
do { 
     //execute code once
} while (false)

虽然这段代码片段可能是解决方案,但包括解释真的有助于提高您的帖子质量。请记住,您正在回答未来读者的问题,而这些人可能不知道您的代码建议原因。 - Allan Cameron
这并没有回答所提出的问题。添加无意义的 do/while 没有任何作用,也绝对不能确保其中的代码仅在包含函数的第一次调用时执行一次。 - underscore_d
你介意解释一下它为什么不执行,为什么它不只执行一次吗?do部分执行,然后while为false,所以do不会再次执行。 除非你多次调用父函数,否则是的,do部分将执行多次。 - Matthew C
它没有回答实际问题:“每个函数可以根据用户输入被调用多次。然而,我需要在应用程序启动时仅执行函数中的一小段代码一次。”这意味着函数可以被调用多次,但其中的一部分必须知道它是否是第一次调用,并且只有在第一次调用时才执行。你的do/while没有任何作用,特别是不能使调用后的代码不运行。你回答了“如何使N行代码只运行一次”的问题,这并不是一个真正的问题,因为答案只是“写一次”...一个虚假的循环完全没有增加任何东西。 - underscore_d
1
这只是一个循环,只有一次迭代,本质上与“//执行一次代码”没有循环完全相同。 - 463035818_is_not_a_number

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接