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ステートメントを評価する前に、その前のステートメントのすべての副作用が完全に評価されたかのようにコードを出力する必要があります。あなたの例は、完全な式の副作用を考慮する前
に評価するため、そのルールを尊重していません。*datastd::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のすべての実行の中で、最大で1つが戻り実行になります。戻り実行がある場合、それが最後のアクティブな実行になります。そして、戻り実行がある場合にのみパッシブ実行があります。[注:パッシブ実行により、他のスレッドは、以前に返された実行によって生成された結果を確実に監視できます。—エンドノート]

3 /同期:任意のonce_flagに対して:すべてのアクティブな実行は合計順序で発生します。アクティブな実行の完了は、この全順序で次の実行の開始と同期します。返される実行は、すべてのパッシブ実行からの返されるものと同期します。

そのため、標準では、ユースケースに合わせて同期が義務付けられています。

于 2012-04-01T13:44:56.530 に答える