鉴于来自here的代码:
class lazy_init
{
mutable std::once_flag flag;
mutable std::unique_ptr<expensive_data> data;
void do_init() const
{
data.reset(new expensive_data);
}
public:
expensive_data const& get_data() const
{
std::call_once(flag,&lazy_init::do_init,this);
return *data;
}
};
我在其他地方也看到了相同模式的一些变体。所以我的问题是:为什么这段代码被认为是保存?为什么编译器在调用std :: call_once之前不能只读取数据并最终得到不正确的数据? e.g
tmp = data.get();
std::call_once(flag,&lazy_init::do_init,this);
return *tmp;
我的意思是我没有发现任何阻碍这种情况的障碍。
答案 0 :(得分:7)
如果允许编译器生成与您描述的匹配的代码,那么用C ++编程基本上是不可能的。
这在§1.9/ 14 程序执行(n3290)中说明:
与全表达相关的每个值计算和副作用在每个值之前排序 计算和副作用与下一个要评估的完整表达相关联。
您的return
语句在前一个完整表达式之后排序。编译器必须输出代码,就好像在评估return语句之前已完全评估了前一个语句的所有副作用。
您的示例不遵守该规则,因为它会在考虑*data
完整表达式的副作用之前评估std::call_once(...)
。
此外,std::call_once
在其描述中有这一点(§30.4.4.2/ 2和3):
2 / 效果:执行不调用其func的call_once是被动执行。调用其func的call_once的执行是一个活动执行。主动执行应调用
INVOKE (DECAY_- COPY ( std::forward<Callable>(func)), DECAY_COPY (std::forward<Args>(args))...)
。如果是这样 对func的调用抛出异常,执行异常,否则返回。异常执行应将异常传播给call_once的调用者。在任何给定的once_flag的call_once的所有执行中:最多一个应该是返回执行;如果有返回执行,则应该是最后一次执行;只有在返回执行时才会执行被动执行。 [注意:被动执行允许其他线程可靠地观察先前返回执行产生的结果。 - 结束说明]3 / 同步:对于任何给定的once_flag:所有活动执行都按总顺序发生;活动执行的完成与此总订单中下一个的开始同步;并且返回的执行与所有被动执行的返回同步。
因此标准要求同步以适合您的用例。