std :: call_once и переупорядочение памяти - PullRequest
4 голосов
/ 01 апреля 2012

Учитывая код от здесь :

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;
    }
};

И я видел несколько вариантов того же шаблона в другом месте также.Итак, мой вопрос: почему этот код считается сохранить?и почему компилятор не может просто прочитать data перед вызовом std :: call_once и в результате получить неверные данные?например,

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

Я имею в виду, что ничего не нашел ни о каких барьерах, которые могли бы этому помешать.

1 Ответ

7 голосов
/ 01 апреля 2012

Программирование на 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.Среди всех исполнений call_once для любого заданного Once_flag: самое большее одно должно быть возвращающим выполнением;если есть возвращающее выполнение, оно должно быть последним активным выполнением;и есть пассивные исполнения, только если есть возвратное исполнение.[ Примечание: пассивные исполнения позволяют другим потокам надежно наблюдать результаты, полученные при предыдущем возврате.- примечание конца ]

3 / Синхронизация : Для любого заданного флаг_выхла: все активные исполнения выполняются в общем порядке;завершение активного выполнения синхронизируется с началом следующего в этом общем порядке;и возвращающее выполнение синхронизируется с возвратом из всех пассивных выполнений.

Таким образом, стандарт требует синхронизации в соответствии с вашим вариантом использования.

...