如何确保 std::call_once 真的只被调用一次

Posted

技术标签:

【中文标题】如何确保 std::call_once 真的只被调用一次【英文标题】:How to ensure std::call_once really is only called once 【发布时间】:2014-11-27 09:34:28 【问题描述】:

我正在使用的一些代码使用 std::call_once 以便某些初始化只发生一次。但是,有些全局对象的构造函数最终会调用初始化代码。

在下面的示例中, call_once 实际上被调用了两次。我猜这是因为 once_flag 构造函数在使用之前还没有运行。有没有办法让一些初始化代码只被调用一次而不必禁止全局变量?

#include <mutex>
#include <iostream>

using namespace std;

void Init();

class Global

public:
    Global()
    
        Init();
    
;

Global global;

once_flag flag;

void Init()

    call_once(flag, []  cout << "hello" << endl;  );




int main(int argc, char* argv[])

    Init();
    return 0;

输出是:

hello
hello

【问题讨论】:

对于 GCC 和 Clang,我只看到一次“hello”输出。 @remyabel - 嗯,我想知道这是否是 micosoft STL 错误。 您的 using namespace std; 破坏了 C++ 的命名空间机制。它还使您的代码更难理解。不要那样做。而是using std::once_flag;等。即导入特定符号;不要转储命名空间。此外,对于标准 C++ 样式,请避免将函数名大写。 【参考方案1】:

根据规范,once_flag 应该有一个简单的 constexpr 构造函数(例如,请参见此处 - http://en.cppreference.com/w/cpp/thread/once_flag )。有了这个,如果它是全局/静态的,它实际上并不是“构造的”(没有执行实际的函数),而更像是“值初始化” - 就像任何全局/静态 POD 类型一样。在这种情况下,不可能让任何构造函数在此 once_flag 正确初始化“之前”运行。鉴于您对使用 MSVC 的评论,我想这可能是实现中的一个错误......

编辑: 根据下面的评论,MSVC 根本不支持constexpr,所以你的选择在这里真的很有限......如果你把所有东西都放在一个文件中,只需将你的once_flag“放在”所有使用它的东西上 -文件中的构造函数按照对象声明的顺序执行。如果您的用户分布在不同的文件中,您唯一的选择是使用提供对静态内部once_flag 的访问的函数 - 就像在这个答案中http://www.parashift.com/c++-faq/static-init-order-on-first-use.html 一样。

【讨论】:

VS2013 根本不支持constexpr 我想知道静态函数是否会出现问题(因为我的 MSVC 版本不支持魔法静态)在面对多个线程时可能会构造两次 once_flag。嗯.. 听起来像是 std::call_once 的工作,以确保不会发生这种情况,嗯,等一下。 :) once_flag 构造函数一点也不简单:它设置内部状态以指示尚未调用任何函数。 @AntonSavin - 它必须是constexpr,这意味着它必须REALLY简单,如此简单以至于从这个角度来看,这种类型几乎可以被视为一个POD . 确实,constexpr once_flag() noexcept; 签名在符合标准的工具链上没有解释的余地​​。

以上是关于如何确保 std::call_once 真的只被调用一次的主要内容,如果未能解决你的问题,请参考以下文章

使用 std::call_once 创建 singleTon 类

C++ 11 call_once

C++ 11 call_once

如何确保一个按钮只被点击“n”次?!安卓工作室

在抛出 'std::system_error' 的实例后调用终止

线程安全的对象生命期管理