【问题标题】:How to execute a piece of code only once?如何只执行一次代码?
【发布时间】:2011-12-07 08:59:49
【问题描述】:

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

【问题讨论】:

  • 你不能把它放在main 的开头(或任何需要的地方)吗?还是在程序的主循环之前?

标签: c++ static global-variables


【解决方案1】:

使用 lambda 函数的精简版:

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

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

【讨论】:

  • 太好了,我在想编译器在初始化静态时已经这样做了,因此我们应该能够从中受益,这是正确的答案(而不是使用标志或run_once()
  • @Bediver 如果您将该 lambda 存储在一个自动变量中而不是立即执行它,它将能够被多次调用。还是那个UB?
  • 这将显示警告'警告:未使用的变量'once' [-Wunused-variable]'。使用 C++17,您可以使用 [[maybe_unused]] 来抑制它。
  • 如果您将由此生成的汇编代码与使用 std::call_once 时生成的汇编代码进行比较,那么这段代码看起来要好很多! godbolt.org/z/s3YPeb
【解决方案2】:

使用 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 相比,这没什么大不了的。)
  • 我正在使用它来产生很大的影响,我唯一要改进的就是如果你在函数中声明你的 once_flag 为静态,它只会被初始化一次并且你的代码在 call_once只会运行一次,但您可以将所有代码保持在同一范围内。
  • 你可以在这里阅读更多关于使用的信息:en.cppreference.com/w/cpp/thread/call_once
【解决方案3】:

使用带构造函数的全局静态对象(在main 之前调用)?或者只是在例程中

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

很少有这样的速度不够快的情况!


附录

在多线程上下文中,这可能还不够:

您可能还对pthread_onceconstructor function __attribute__ of GCC 感兴趣。

使用 C++11,您可能需要std::call_once

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

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

【讨论】:

  • 这也是我的想法,但在阅读@KirilKirov 的评论后,我不得不把头撞到一些石雕上。干杯,
  • 这在多线程设置中不起作用,但应该适用于大多数用例
  • 确保将该 bool 初始化为 false! static bool initialized(false); 否则你永远不知道分配后内存中的内容。
  • AFAIK,static 变量被初始化为全零位,对于bool,这是false
  • 从 C++11 开始这是线程安全的。是的,静态变量是零初始化的。但是,我仍然认为始终明确初始化原始类型的习惯是好的。这样,如果它被重构为不被隐式初始化,你以后就不会遇到恐惧。
【解决方案4】:

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

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

     ...
}

【讨论】:

    【解决方案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; } ())
    

    可以放在任何你想要的地方call-by-need:

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

    为了更好地衡量,atomics 缩短了表达式并使其成为线程安全的:

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

    【讨论】:

    • 我欢迎建设性的批评,所以如果您认为这个答案值得一票否决,请解释原因。
    【解决方案6】:

    你能做到吗

    有一个函数可以返回一个 bool 或一些称为 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;
    }
    

    【讨论】:

    • 它是否执行一次,因为“静态只会被初始化一次”??
    【解决方案7】:
    如果您不需要完全线程安全的解决方案,

    std::call_once() et al. 可能有点过头了。

    如果没有,我们可以在使用 C++17 的 initialisation-within-ifstd::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++:可能性是无限的!

    【讨论】:

    • 欢迎投反对票的人赐教,现在我修正了一个简单的错字,这是我唯一能看到的错误。
    • 我不是反对者,但您的代码不正确。是的 static auto called 以线程安全的方式初始化为 false。但是对std::exchange 的并发调用不能保证被序列化。因此,您可以多次致电do_something_expensive
    • OP 没有指定需要线程安全的解决方案,如果不需要也可以。这并不意味着“我的代码不正确”;这仅意味着根据每个人的确切情况,这对每个人来说可能还不够……无论如何,我已经进行了编辑以澄清。
    • 好吧,如果这不是线程安全的解决方案,那么我不会使用 std::exchange:if ( static auto called = false; !called ) { do_something_expensive();called = true; }
    • 除了个人风格偏好之外,这是否重要?我更喜欢它原来的样子。
    【解决方案8】:

    另一个简单的解决方案是:

    #define execute_once if(static bool b = false; b) ; else if((b = true))
    

    这样使用:

    execute_once std::cout << "Hi mum!\n";
    

    或:

    execute_once
    {
        std::cout << "These statements are ";
        std::cout << "only executed once\n";
    }
    

    显然,这不是线程安全的。

    【讨论】:

      【解决方案9】:
      do { 
           //execute code once
      } while (false)
      

      【讨论】:

      • 虽然此代码 sn-p 可能是解决方案,但包含解释确实有助于提高帖子的质量。请记住,您是在为将来的读者回答问题,而这些人可能不知道您提出代码建议的原因。
      • 这不能回答所提出的问题。添加毫无意义的do/while 什么都不做,当然也不能确保其中的代码在第一次调用包含函数时只执行一次。
      • 您介意解释一下它是如何不执行任何操作的吗? do 部分执行,然后 while 为 false,因此 do 不会再次执行。除非您多次调用父函数,否则是的,do 部分将执行多次。
      • 它没有回答实际的问题:“每个函数可以根据用户输入多次调用。但是我只需要在一个函数中执行一小段代码一次,最初在应用程序启动时。”这意味着该函数可以被多次调用,但它的一部分必须知道它是否是第一次调用并且只有在是时才执行。你的 do/while 什么也没增加,尤其是在第一次不运行代码之后不要打电话。您回答了“我如何让 N 行代码只运行一次”,这不是真正的 Q,因为答案只是“编写一次”......假循环恰好添加了零。
      • 这只是一个单次迭代的循环,它与没有循环的// execute code once基本完全相同
      猜你喜欢
      • 2014-07-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多