0

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.
André Offringa
  • 362
  • 2
  • 7
  • 1
    Can your other static objects call `get_A` in their own constructors, or is that what you're trying to avoid? Possible [duplicate](https://stackoverflow.com/questions/335369/finding-c-static-initialization-order-problems/335746#335746) if that works. – Phil M Mar 01 '19 at 23:12
  • *get_A() is used in the destructor of other static classes that are destructed later.* Did you mean to say *get_A() is used in the constructor of other static classes that are destructed later.*? – NathanOliver Mar 01 '19 at 23:18
  • @NathanOliver no, `get_A()` is actually called in the destructor of other static classes. – André Offringa Mar 01 '19 at 23:38
  • @PhilM Not all other objects call `get_A` in their constructor, and I don't want to change those objects. Indeed, making sure `get_A` is called in every constructor would otherwise be a good answer. The link you post is very related, so thanks for that. – André Offringa Mar 01 '19 at 23:47
  • 1
    If you are relying on the order or sequencing of construction (or destruction) of objects with static storage duration, then you have a problem. The construction order is unspecified for statics in different compilation units (aka source files). The destruction order is the reverse of the construction order but, since that is unspecified for statics in different compilation unit, the destruction order is also unspecified. Look up "static initialisation fiasco" for more discussion. – Peter Mar 02 '19 at 00:42
  • @Peter I'm not trying to rely on the order of sequencing, I would like to know whether the first version of the code is a "legal" way around the destruction ordering problem (by not destructing the object at all), and whether there is a way to make that thread safe. I was worrying that it is not legal because the storage could be reclaimed after exit but before destruction of other object, but I am starting to think that the storage stays available until the end of the program, and hence it is legal. – André Offringa Mar 02 '19 at 07:07

1 Answers1

0

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).

André Offringa
  • 362
  • 2
  • 7