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 );
}
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 );
}
对于您的示例用法,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
.
两个代码片段具有相同的行为,即使在初始化期间出现异常时也是如此。
这个结论是基于(我的解释)来自 c++11 标准(草案 n3337)的以下引用:
具有静态存储持续时间 (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 {...};
只执行(成功)一次。
不调用其函数的 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{...};
都只执行(成功)一次。