You can get around static initialization and destruction ordering issues
by
using a strongly thread-safe smart pointer to manage the singleton. The
pseudo-code would be something like the following pseudo-code:
_____________________________________________________________________ [...]
_____________________________________________________________________
This is strongly thread-safe and will always work no matter how the
static
ctor/dtor ordering comes out. The caveat, well, it's definitely not as
efficient as using a raw pointer and explicitly leaking the singleton..
There are so many things wrong with that code sample, I don't even
know where to start. (Exaggeration. I do know where to start.)
Actually, well... How much experience do you have wrt multi-threading
issues?
Firstly and most importantly, you're using double checked locking,
which is broken in effectively all C++ implementations.
I explicitly stated that one can:
"get around static initialization and destruction ordering issues by using a
strongly thread-safe smart pointer to manage the singleton"
Do you have any idea what I am writing about here? Go ahead and put it on
the self because this discussion can get advanced rather quickly!
Ok, so apparently your "strong thread-safe" pointer class has barrier
semantics on "operator!" and "reset". If that's the case, then yes, it
does make this double checked locking safe. I'm sorry here.
However, next time please emphasize that "my pointer class has barrier
semantics on operator! and reset". You should see the recent post
about what "thread-safe" means in terms of boost shared pointer. The
general result of that thread was several people 'knew' the definition
of thread-safe in the context of boost shared pointer, except that
each definition was different.
Quoting Open Group
In cases where default mutex attributes are appropriate, the macro PTHREAD_MUTEX_INITIALIZER can be used to initialise mutexes that are statically allocated. The effect is equivalent to dynamic initialisation by a call to pthread_mutex_init() with parameter attr specified as NULL, except that no error checks are performed.
Now, I admit this has always been vague to me. Let's see. It says that
"the effect is equivalent to dynamic initialization by a call to
pthread_mutex_init". Unfortunately, I'm used to the C++ world where
\dynamic initialization\ means something very specific. Under that
definition, using it as the initializor of a function local static
would be a race condition. However, it seems that C does not allow
function local statics to be initialized with anything but a constant
expression (as determined by comeau online), making the code what C++
calls \static initialization\. I am thus entirely wrong on this point.
I'm sorry.
And yes, I rarely use posix directly. I'm in a world where my code has
to work on windows and posix, and all windows mutexes require runtime
init, so I have to program to the lowest common denominator. I'm still
wrong, just explaining my background.
--
That leaves us with your code, which while being obfuscated, is also
correct, given a certain latitude in your favor aka the exact
definition of a "strongly thread-safe pointer class". If your pointer
class just uses pthread mutexes, then your example, once simplified of
redundant mutexes, is equivalent to
struct pthread_mutex_guard
{ pthread_mutex_guard(pthread_mutex_t & y) : x(y) { pthread_mutex_lock
(x); }
~pthread_mutex_guard() { pthread_mutex_unlock(x); }
pthread_mutex_t & x;
};
template <typename T>
T& once()
{
static T* t = 0;
{
static pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_guard g(m);
if (0 == t)
t = new T;
}
return *t;
}
If your pointer class makes use of read and write memory barriers,
then it's equivalent to
struct pthread_mutex_guard
{ pthread_mutex_guard(pthread_mutex_t & y) : x(y) { pthread_mutex_lock
(x); }
~pthread_mutex_guard() { pthread_mutex_unlock(x); }
pthread_mutex_t & x;
};
template <typename T>
T& once()
{
static T* t = 0;
if (0 == t)
{
static pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_guard g(m);
if (0 == t)
{
T* tmp = new T;
write_barrier(); /* platform specific */
t = tmp;
}
}
read_barrier(); /* platform specific */
return *t;
}
Either way, in this context of trying to demonstrate the proper way of
writing a singleton, one should not hide away crucial implementation
details as you did. It is not useful to other readers to say "here's
what it looks like, except for the important implementation details".
You used something that looked very much like the "common" double
checked locking anti-pattern, but with "hidden magic" which made it
correct. That will only help perpetuate the myth that the "common"
double checked locking anti-pattern is correct.
PS: Note that both of my solutions in this post are relatively devoid
of error checking. You should probably at least assert on the return
code of pthread functions.
PPS: All windows standard mutexes have runtime init, so you cannot do
either of these approaches without significant modifications, or
without rolling your own mutex built on some of the atomic primitives
like test and swap. See:
http://www.ddj.com/cpp/199203083?pgno=7
for a good discussion on how to do this.
Alternatively, use a namespace scope variable to force construction
before main (or before dlopen returns for static init of a dll) to
"guarantee" correctness as demonstrated in countless posts in this
thread.