std :: call_once和内存重新排序

时间:2012-04-01 13:33:48

标签: c++ c++11 memory-fences

鉴于来自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;

我的意思是我没有发现任何阻碍这种情况的障碍。

1 个答案:

答案 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:所有活动执行都按总顺序发生;活动执行的完成与此总订单中下一个的开始同步;并且返回的执行与所有被动执行的返回同步。

因此标准要求同步以适合您的用例。