Я наткнулся на некоторый код, который имел функцию, аналогичную следующей, с использованием некоторого шаблонного класса A
:
template<typename X>
A<X>* get_A() {
static char storage[sizeof(A<X>)];
static A<X>* ptr = nullptr;
if(!ptr) {
new (reinterpret_cast<A<X>*>(storage)) A<X>();
ptr = reinterpret_cast<A<X>*>(storage);
}
return ptr;
}
Мне нужно было сделать этот поток инициализации безопасным, поэтому я изменил его на:
A<X>* get_A() {
static A<X> a;
return &a;
}
Это, однако, вызывает ошибку по умолчанию: get_A()
используется в деструкторе других статических классов, которые уничтожаются позже.Сложная конструкция A
, очевидно, существует для того, чтобы продлить срок жизни A
за пределы разрушения любого другого объекта, и сама по себе никогда не разрушается.Я заметил, что https://en.cppreference.com/w/cpp/utility/program/exit говорит
Если статический объект локального (блочного объема) объекта был уничтожен, а затем эта функция вызывается из деструктора другого статического объекта и потока управленияпроходит через определение этого объекта (или если он используется косвенно, через указатель или ссылку), поведение не определено.
Поскольку статическое хранилище и ptr не являются «объектами», я думаю, что этоПравило не делает поведение первой версии неопределенным, но оно предотвращает создание static std::mutex
внутри функции.Поэтому у меня следующие вопросы:
- Согласно стандарту C ++ 11 (или новее), учитывая, что
get_A
вызывается из деструктора объекта со статическим временем жизни, это первая версия воднопоточная программа при любых обстоятельствах действительно допустима или может навязывать неопределенное поведение? - Как сделать этот поток безопасным, не вызывая неопределенное поведение и не изменяя использование
get_A
другими классами?Я предпочитаю не иметь инициализирующего кода для каждого возможного X
, с которым создается экземпляр шаблона A
, потому что A
создается с различными типами.Если только это не окажется единственным хорошим решением.