Reputation: 9733
The "magic static" singleton approach is generally quite effective:
T& instance() {
static T inst;
return inst;
}
In a threadsafe way, it creates the T
on first call and that object lives until program shutdown. However, if this is a logging object, and if there are background threads that outlast main, this can break down. I see some mention of the problem, but not of solutions:
Is this a viable approach?:
/*static*/ T& T::instance() {
static auto inst = std::make_shared<T>();
static thread_local auto threadInst = inst; // Hope this call gets hit before `inst` goes out of scope.
return *threadInst;
}
As I understand it, so long as each thread calls T::instance()
before main exits, that thread will correctly set up threadInst
which will keep the object alive as long as the calling thread is running.
Questions:
T::instance()
is first called by each thread after main()
starts and before main()
finishes, will the above avoid UB?inst
to communicate that with other threads.Upvotes: 0
Views: 140
Reputation: 7528
The reason the other questions don't provide solutions is that it impossible to do usefully in most circumstances. Allowing threads to live past main is difficult to write and maintain. Some of the rules are:
[basic.start.term/4] If a function contains a block variable of static or thread storage duration that has been destroyed and the function is called during the destruction of an object with static or thread storage duration, the program has undefined behavior if the flow of control passes through the definition of the previously destroyed block variable.
So, if you cannot guarantee that you execute before static storage destruction, you cannot use a function-local static.
[basic.start.term/6] If there is a use of a standard library object or function not permitted within signal handlers that does not happen before completion of destruction of objects with static storage duration and execution of
std::atexit
registered functions, the program has undefined behavior.
So, if you cannot guarantee that you execute before static storage destruction, you also cannot use most of the standard library.
[support.signal/3.1] [Note 1: This implicitly excludes the use of new and delete expressions that rely on a library-provided memory allocator. — end note]
So you cannot directly or indirectly use a standard new or delete.
Back to your example:
/*static*/ T& T::instance() {
static auto inst = std::make_shared<T>();
static thread_local auto threadInst = inst; // Hope this call gets hit before `inst` goes out of scope.
return *threadInst;
}
Unless you coordinate with the thread, so that this is called and the thread_local
is constructed before detach
, you have just replaced one race with another. To do something this way robustly, you have to wait on a condition variable for the thread to notify you that the thread_local is initialized, before you detach. However, if you do this, it will be simpler usually to pass the pointer to the thread directly. Also, shared_ptr
is not allowed in a signal handler, so it is not a type you can use for this purpose.
So, here would be my guidelines:
Upvotes: 2