19

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

2 回答 2

15

对于您的示例用法,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.

于 2013-07-01T17:20:29.417 回答
8

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

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

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

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

这意味着在:

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

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

  • 2 第30.4.4.2 节函数调用一次状态:

不调用其函数的 call_once 执行是被动执行。调用其函数的 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 {....}; } );

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

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

于 2013-07-01T16:04:28.700 回答