std :: call_once和函数级静态初始化之间有什么区别

时间:2013-07-01 14:58:56

标签: multithreading c++11

1)std :: call_once

A a;
std::once_flag once;

void f ( ) {
    call_once ( once, [ ] { a = A {....}; } );
}

2)功能级静态

A a;

void f ( ) {
    static bool b = ( [ ] { a = A {....}; } ( ), true );
}

2 个答案:

答案 0 :(得分:13)

对于您的示例用法,hmjd的答案完全解释了没有区别(once_flag案例中所需的其他全局call_once对象除外。)但是,call_once案例是更灵活,因为once_flag对象不依赖于单个范围。例如,它可以是一个类成员,并由多个函数使用:

class X {
  std::once_flag once;

  void doSomething() {
    std::call_once(once, []{ /* init ...*/ });
    // ...
  }

  void doSomethingElse() {
    std::call_once(once, []{ /*alternative init ...*/ });
    // ...
  }
};

现在,根据首先调用的成员函数,初始化代码可能不同(但对象仍然只会被初始化一次。)

因此,对于简单的情况,本地静态可以很好地工作(如果您的编译器支持),但有一些不太常见的用法可能更容易使用call_once实现。

答案 1 :(得分:6)

两个代码段都具有相同的行为,即使在初始化期间抛出异常时也是如此。

这个结论是基于(我的解释)c ++ 11标准(草案n3337)中的以下引用:

  • 1节 6.7声明声明第4条规定:
  

在执行任何其他初始化之前,将执行具有静态存储持续时间(3.7.1)或线程存储持续时间(3.7.2)的所有块范围变量的零初始化(8.5)。具有静态存储持续时间的块范围实体的常量初始化(3.6.2)(如果适用)在首次输入块之前执行。允许实现在静态或线程存储持续时间内执行其他块范围变量的早期初始化,条件是允许实现在命名空间范围内静态初始化具有静态或线程存储持续时间的变量(3.6.2)。否则这个变量在第一次控制通过其声明时被初始化;这样的变量在初始化完成后被认为是初始化的。如果通过抛出异常退出初始化,则初始化未完成,因此下次控制进入声明时将再次尝试初始化。如果控件在初始化变量时同时进入声明,则并发执行应等待初始化完成.88 如果控件在初始化变量时以递归方式重新进入声明,则行为未定义。 / p>

这意味着:

void f ( ) {
    static bool b = ( [ ] { a = A {....}; } ( ), true );
}

b保证只被初始化一次,这意味着lambda仅执行一次(成功),意味着a = A {...};仅执行一次(成功)。

  • 2部分 30.4.4.2函数调用一次状态:
  

执行不调用其func的call_once是被动执行。调用其func的call_once的执行是一个活动执行。活动执行应调用INVOKE(DECAY_COPY(std :: forward(func)),DECAY_COPY(std :: forward(args))...)。如果对func的这种调用抛出异常,则执行异常,否则返回。异常执行应将异常传播给call_once的调用者。 在任何给定的once_flag的call_once的所有执行中:最多一个应该是返回执行; 如果有返回执行,它应该是最后一个活动执行;并且只有在返回执行时才会执行被动执行。

这意味着:

void f ( ) {
    call_once ( once, [ ] { a = A {....}; } );

std::call_once的lambda参数仅执行一次(成功),意味着a = A {...};仅执行一次(成功)。

在这两种情况下a = A{...};仅执行一次(成功)。