C ++ 11 устраняет необходимость в блокировке. Параллельное выполнение должно ожидать, если статическая локальная переменная уже инициализируется.
§6.7 [stmt.dcl] p4
Если элемент управления вводит объявление одновременно во время инициализации переменной, параллельное выполнение должно ожидать завершения инициализации.
Для C ++ 03 у нас есть это:
§6.7 [stmt.dcl] p4
Инициализация нуля (8.5) всех локальных объектов со статической продолжительностью хранения (3.7.1) выполняется перед любой другой инициализацией. Локальный объект типа POD (3.9) со статической продолжительностью хранения, инициализированной с помощью константных выражений, инициализируется перед первым вводом его блока. Реализация может выполнять раннюю инициализацию других локальных объектов со статической продолжительностью хранения при тех же условиях, что и
реализация допускает статическую инициализацию объекта со статической продолжительностью хранения в области пространства имен (3.6.2). В противном случае такой объект инициализируется при первом прохождении управления через его объявление ;
Последняя часть важна, поскольку она относится к вашему коду. Когда элемент управления сначала вводит get_class_instance()
, он сначала проходит через инициализацию критической секции, затем через объявление синглтона (как таковое инициализирует его внутри критической секции), а затем проходит через деинициализацию критической секции.
Так что с теоретической точки зрения ваш код должен быть безопасным.
Теперь это можно улучшить, чтобы не вводить критическую секцию при каждом вызове функции. Основная идея @Chethan - это звук, поэтому мы будем основываться на этом. Однако мы также собираемся избегать динамического распределения. Однако для этого мы полагаемся на Boost.Optional:
#include <boost/optional.hpp>
Class& get_class_instance() {
static boost::optional<Class> c;
static bool inited;
if (!inited){
EnterCriticalSection(&cs);
if(!c)
c = Class(data);
LeaveCriticalSection(&cs);
inited = true;
}
return *c;
}
Boost.Optional избегает инициализации по умолчанию, а двойная проверка избегает ввода критической секции при каждом вызове функции. Эта версия, однако, вводит конструктор копирования Class
в назначении. Решением для этого являются фабрики на месте:
#include <boost/utility/in_place_factory.hpp>
#include <boost/optional.hpp>
Class& get_class_instance() {
static boost::optional<Class> c;
static bool inited;
if (!inited){
EnterCriticalSection(&cs);
if(!c)
c = boost::in_place(data);
LeaveCriticalSection(&cs);
inited = true;
}
return *c;
}
Я благодарю @R. Мартиньо Фернандес и @Ben Voigt, которые сотрудничали в этом окончательном решении. Если вы заинтересованы в этом процессе, не стесняйтесь взглянуть на стенограмму .
Теперь, если ваш компилятор уже поддерживает некоторые функции C ++ 11, но не статическую инициализацию, вы также можете использовать std::unique_ptr
в сочетании с размещением нового и статически выровненным буфером:
#include <memory> // std::unique_ptr
#include <type_traits> // alignment stuff
template<class T>
struct destructor{
void operator(T* p) const{
if(p) // don't destruct a null pointer
p->~T();
}
};
Class& get_class_instance() {
typedef std::aligned_storage<sizeof(Class),
std::alignment_of<Class>::value>::type storage_type;
static storage_type buf;
static std::unique_ptr<Class, destructor> p;
static bool inited;
if (!inited){
EnterCriticalSection(&cs);
if(!p)
p.reset(new (&buf[0]) Class(data));
LeaveCriticalSection(&cs);
inited = true;
}
return *p;
}