В чем разница между std:: call_once и статической инициализацией на уровне функций - программирование
Подтвердить что ты не робот

В чем разница между std:: call_once и статической инициализацией на уровне функций

1) std:: call_once

A a;
std::once_flag once;

void f ( ) {
    call_once ( once, [ ] { a = A {....}; } );
}

2) статический статический уровень

A a;

void f ( ) {
    static bool b = ( [ ] { a = A {....}; } ( ), true );
}
4b9b3361

Ответ 1

В вашем примере использования, hmjd ответ полностью объясняет, что нет разницы (кроме дополнительного глобального once_flag объекта, необходимого в случае call_once.) Однако случай call_once более гибкий, поскольку once_flag объект не привязан к одной области. В качестве примера он может быть членом класса и использоваться более чем одной функцией:

class X {
  std::once_flag once;

  void doSomething() {
    std::call_once(once, []{ /* init ...*/ });
    // ...
  }

  void doSomethingElse() {
    std::call_once(once, []{ /*alternative init ...*/ });
    // ...
  }
};

Теперь, в зависимости от того, какая функция-член вызывается сначала, код инициализации может быть другим (но объект будет только один раз инициализироваться.)

Итак, для простых случаев локальный статический файл работает хорошо (если поддерживается вашим компилятором), но есть несколько менее распространенных применений, которые могут быть проще реализовать с помощью call_once.

Ответ 2

Оба фрагмента кода имеют одинаковое поведение, даже при наличии исключений, которые были выбраны во время инициализации.

Этот вывод основан на (моей интерпретации) следующих цитатах из стандарта С++ 11 (проект n3337):

  • 1 Раздел 6.7 В заявлении 4 говорится:

Нулевая инициализация (8.5) всех переменных объема блока со статической продолжительностью хранения (3.7.1) или продолжительностью хранения потоков (3.7.2) выполняется до любой другой инициализации. Постоянная инициализация (3.6.2) объекта с областью действия блока со статической продолжительностью хранения, если это применимо, выполняется до того, как ее блок будет введен первым. Реализации разрешено выполнять раннюю инициализацию других переменных области блока со статическими или потоками хранения потоков при тех же условиях, что реализации разрешено статически инициализировать переменную со статикой или временем хранения потоков в пространстве имен (3.6.2). В противном случае такая переменная инициализируется, первый элемент управления проходит через его объявление; такая переменная считается инициализированной после завершения ее инициализации. Если инициализация завершается путем исключения исключения, инициализация не завершена, поэтому она будет снова проверена, когда следующий элемент управления войдет в объявление. Если элемент управления входит в объявление одновременно при инициализации переменной, одновременное выполнение должно ждать завершения инициализации .88 Если элемент управления повторно вводит декларацию рекурсивно во время инициализации переменной, поведение undefined.

Это означает, что в:

void f ( ) {
    static bool b = ( [ ] { a = A {....}; } ( ), true );
}

b гарантированно будет инициализирован только один раз, что означает, что lambda выполняется (успешно) только один раз, то есть a = A {...}; выполняется (успешно) только один раз.

  • 2 Раздел 30.4.4.2 Назначение состояния вызова:

Выполнение call_once, которое не вызывает его func, является пассивным исполнением. Выполнение call_once, вызывающего его func, является активным исполнением. Активное выполнение должно вызвать INVOKE (DECAY_COPY (std:: forward (func)), DECAY_COPY (std:: forward (args))...). Если такой вызов func вызывает исключение, выполнение является исключительным, в противном случае оно возвращается. Исключительное исполнение должно распространять исключение на вызывающего абонента call_once. Среди всех исполнений call_once для любого заданного параметра once_flag: самое большее должно быть возвратным исполнением;, если есть возвращающее выполнение, оно должно быть последним активным исполнением; и существуют пассивные исполнения только в случае выполнения возвращаемого выполнения.

Это означает, что в:

void f ( ) {
    call_once ( once, [ ] { a = A {....}; } );

аргумент лямбда для std::call_once выполняется только один раз, то есть a = A {...}; выполняется (успешно) только один раз.

В обоих случаях a = A{...}; выполняется (успешно) только один раз.