4

鉴于此处的代码:

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 之前读取数据并最终得到不正确的数据?例如

tmp = data.get();
std::call_once(flag,&lazy_init::do_init,this);
return *tmp;

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

4

1 回答 1

7

如果允许编译器生成与您描述的代码相匹配的代码,那么用 C++ 编程基本上是不可能的。

这在 §1.9/14程序执行(n3290) 中有说明:

与完整表达式关联的每个值计算和副作用在与要评估的下一个完整表达式关联的每个值计算和副作用之前排序。

您的return语句在前面的完整表达式之后排序。编译器必须输出代码,就好像在它评估 return 语句之前,前面语句的所有副作用都已被完全评估。
您的示例不遵守该规则,因为它*data在考虑std::call_once(...)完整表达式的副作用之前进行了评估。

此外,std::call_once它的描述中有这个(§30.4.4.2/2 和 3):

2/效果:不调用其函数的 call_once 执行是被动执行。调用其函数的 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:所有活动的执行都按总顺序发生;活动执行的完成与此总顺序中下一个执行的开始同步;并且返回的执行与所有被动执行的返回同步。

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

于 2012-04-01T13:44:56.530 回答