简体   繁体   中英

Can a static array of chars be (thread-safe) used to prolong life of an object?

I came across some code that had a function similar to the following, using some templated class 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;
}

I needed to make this initialization thread safe, so I changed this to:

A<X>* get_A() {
  static A<X> a;
  return &a;
}

This however causes a segfault: get_A() is used in the destructor of other static classes that are destructed later. The complicated construction of A is apparently there to extend the lifetime of A beyond the destruction of any other object, and is itself never destructed. I noticed that https://en.cppreference.com/w/cpp/utility/program/exit says

If a function-local (block-scope) static object was destroyed and then that function is called from the destructor of another static object and the control flow passes through the definition of that object (or if it is used indirectly, via pointer or reference), the behavior is undefined.

Since the static storage and ptr are not 'objects', I think this rule does not make the first version undefined behavior, but it does prevent constructing a static std::mutex inside the function. My questions therefore are:

  • According to the C++11 standard (or newer), given that get_A is called from the destructor of an object that has static lifetime, is the first version in a single-threaded program under all circumstances indeed legal or may it impose undefined behaviour?
  • How can I make this thread safe without invoking undefined behaviour and without having to change the use of get_A by other classes? I prefer not to have initializing code for every possible X that template A is instantiated with, because A is instantiated with many different types. Unless that turns out to be the only good solution.

I found a solution to do this:

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

I've changed the char array that is used in the question into a std::aligned_storage to make sure the array has the proper alignment. In C++17, it would probably require std::launder , but I'm using C++11. Type A and the function can of course be templated as in the original question, but I kept the example as simple as possible.

It's still a bit of a hack, but as far as I can tell this is thread safe, and it allows initialiation of a static object, without ever destructing it and without leaking memory (of course, as long as A doesn't own memory).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM