Может ли статический массив символов (потокобезопасный) использоваться для продления жизни объекта? - PullRequest
0 голосов
/ 02 марта 2019

Я наткнулся на некоторый код, который имел функцию, аналогичную следующей, с использованием некоторого шаблонного класса 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 создается с различными типами.Если только это не окажется единственным хорошим решением.

1 Ответ

0 голосов
/ 11 марта 2019

Я нашел решение для этого:

A* get_A()
{
  static typename std::aligned_storage<sizeof(A), alignof(A)>::type storage;
  static A* ptr = new (reinterpret_cast<A*>(&storage)) A();
  return ptr;
}

Я изменил массив char, который используется в вопросе, на std::aligned_storage, чтобы убедиться, что массив имеет правильное выравнивание.В C ++ 17 это, вероятно, потребует std::launder, но я использую C ++ 11.Наберите A, и функцию, конечно, можно задавать шаблонами, как в первоначальном вопросе, но я сохранил пример как можно более простым.

Это все еще немного хак, но, насколько я могу судить, этоПотокобезопасен и позволяет инициализировать статический объект, не разрушая его и не теряя память (конечно, если A не владеет памятью).

...