Singleton overview
I have a 481 line text document at 80 characters per line. It's a
brief overview of globals, singletons, the static initialization order
fiasco, the static de-initialization order fiasco, and double checked
locking. I'll be basing a presentation to my colleagues on this little
draft. In it, I do a quick overview of the concepts, what works, what
doesn't work, and why. It contains nothing new, and it's not
particularly formal.
I was wondering if this was a proper venue for posting such a thing
for comments and criticism, and if not, what would be?
In the interests of brevity, the things I'm most interested in are:
-1- (A little beyond the scope of C++98, but in scope for C++0x). If
one programs with the Java 1.5 memory model guidelines in mind, will
this work with C++98 on all major operating systems with all major
threading libraries? C++0x?
-2- I'm paranoid when it comes to problems like this. I dislike how
the standard singleton interface returns a pointer to the singleton. I
have thought about making the suggestion that instead of a getInstance
function, you declare global functions in the header file. These
global functions are defined in the cpp file to use the singleton.
Thus the user is unable to get a reference to the singleton, and thus
cannot cause static de-initialization order problems.
-3- I know the C++98 standard does not address threads, but if statics
are initialized during a multi-threaded situation, will all major
compilers still destroy them in the exact opposite order? This implies
some locking overhead during the "first pass" over function local
statics, as the compiler has to create a hidden global list of static
objects to use to determine the order of static de-initialization, and
update it whenever a static constructor exits. As two different
function local statics can be initialized concurrently, access to this
list must be synchronized for correct results.
-4- I believe I have an implementation for a singleton in C++98 so
that the following are true. (I was wondering if I missed anything.)
-4-a- It is lazily initialized. It will be initialized on first use,
which may be after main has started.
-4-b- It is thread-safe, a.k.a. the singleton is guaranteed to be
constructed only once.
-4-c- There is synchronization at most for the first call from each
thread. Later calls have no synchronization.
-4-d- It will be destroyed during static de-initialization.
-4-e- It does not any static initialization order problems nor static
de-initialization order problems. (Assuming this singleton does not
use other singletons. If it does, as long as it references all needed
singletons in its constructor, all static order problems are gone.)
-4-f- It is otherwise correct.
Here is the "implementation", which may require implementation
specific code, but I don't think so. What it does is replace
synchronization overhead in all cases with an extra level of function
indirection in all cases.
It was inspired when a Java friend of mine claimed that Java could
have a singleton in a multi-threaded environment with all of the
guarantees I've listed (except 4-d, static de-initialization). I at
first said impossible, but it struct me how the JVM could do it by
swapping out a virtual function pointer in the virtual function table
after the first call. I do something similar here.
//.hpp file
class Mutex {}; //from another header file
class Guard { public: Guard(Mutex&); }; //from another header file
void function_1();
void function_2();
//.cpp file
#include <cassert>
namespace
{
class T
{
public:
void function_1();
void function_2();
};
Mutex & functionContainingStaticMutex()
{ static Mutex m;
return m;
}
struct ForceInstantionOfMutex
{ ForceInstantionOfMutex()
{ functionContainingStaticMutex(); }
} instance_ForceInstantionOfMutex;
T& synchronizedCall();
T& unsynchronizedCall();
//initialization of POD types with constant expressions
//is guaranteed to occur before runtime.
T& (*funcPtr)() = &synchronizedCall;
T& functionContainingStaticT()
{ static T t;
return t;
}
T& synchronizedCall()
{ Guard g(functionContainingStaticMutex());
T& t = functionContainingStaticT();
funcPtr = unsynchronizedCall;
//You need a memory barrier here. You need an
implementation
//specific instruction to prevent another core from seeing
//funcPtr == &unsynchronizedCall and t partially
initialized.
//The problem is that a core may load funcPtr into its
cache
//without calling getSingleton(). For example, it may load
//the address right next to &funcPtr during some other
operation,
//and it just happens to load the addresses around it as
well
//to the cache as a kind of look-ahead-like optimization.
return t;
}
T& unsynchronizedCall()
{ return functionContainingStaticT();
}
T& getSingleton()
{ return funcPtr();
}
}
void function_1()
{ getSingleton().function_1();
}
void function_2()
{ getSingleton().function_2();
}
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]