The static initialization order fiasco is a well known problem, involving static or global objects with a constructor that uses one or more other static or global objects, so that the order in which the static and global objects are initialized is important.
[ |
Note that anything that can be initialized with an assignment, for example:
struct Foo x = { 'a', 10, "Hello" };
is not the problem; such initialization data resides in the .data segment and will never
depend on other static or global objects. ]
|
The generally accepted solution for this problem is to wrap all static objects in a static or global function and use that to access the objects.
The disadvantage of that method is that the compiler will check whether or not the static object has been initialized every time you call its access function.
For example, consider the following code that makes use of this wrapping technique
// File x.cpp #include "Fred.hpp" Fred& x(void) { static Fred* ans = new Fred(); return *ans; } // File Barney.cpp #include "Barney.hpp" Barney::Barney() { // ... x().goBowling(); // ... }
After optimization, the assembly code for x().goBowling(); inside Barney::Barney() reads
cmpl $0,_.tmp_0.3 jne .L5 movl $1,_.tmp_0.3 pushl $1 call __builtin_new movl %eax,ans.2 addl $4,%esp .L5: pushl ans.2 call goBowling__4Fred movl 8(%ebp),%eax
The check, whether or not the static Fred object is initialized already, is marked red. This check is necessary because in this example (directly from the C++ FAQ) we access x (Fred) from within the constructor of a global object (Barney). The created Fred object can only be accessed by calling Fred::x(), which means that every time you use it, the application will perform the check whether or not the static object is already initialized.
There is an alternative solution for the static initialization order fiasco. While the solution given in the C++-FAQ is not error prone, the problem is not that the developer is a total clueless newbie that has to be protected against himself. The problem is that the order of initialization of global objects is rather arbitrary (compiler implementation dependant).
The Carlo Wood solution to the static initialization order fiasco is to use two different access functions: one with check that is used inside constructors of static and global objects; and one without check that is used everywhere else (all code that can be called from main().
Applying this idea to the above example results in code like
// File x.hpp #include "Fred.hpp" class GlobalFred { private: static Fred* ans; public: static Fred& x(void); { if (!ans) ans = new Fred; return *ans; } static Fred& x_without_check(void) { return *ans; } }; // File x.cpp #include "x.hpp" Fred* GlobalFred::ans;
Note the need for a new class: GlobalFred. The static pointer ans is now a private member of this class so that we can access it from both, x(void) as well as x_without_check(void).
Inside constructors the safe GlobalFred::x() is used. But outside constructors the member function GlobalFred::x_without_check() can be used instead, omitting the redundant NULL check for GlobalFred::ans.
When a non-constructor function that uses GlobalFred::x_without_check() is called from a constructor of a global object, then this constructor should simply first call GlobalFred::x(). Consider the function
void goOut(void) { if (GlobalFred::x_without_check().goBowling()) bowling = true; }
This function happily assumes that the Fred object is already initialized. When this function is accessed from a constructor, you need to do something like
Barney::Barney() { // goOut uses GlobalFred GlobalFred::x(); // Instantiate GlobalFred::ans goOut(); // ... }
The best is to completely avoid the need for static or global objects. For those cases that they can not be avoided libcw provides a template to help with the declaration of global objects. There is no need for static objects: use namespaces to avoid name collisions.
template<class TYPE, int instance, class CONVERTER = GlobalConverterInt> class Global
This class provides access to an object of type TYPE the instance of which is determined by instance, an integer constant. The CONVERTER class converts the given instance integer into data of an arbitrary type that is passed to the constructor of TYPE when the object is created.
Constructors of static, global and Global<> classes need to access such objects through the member function Global<TYPE, instance, CONVERTER>::instantiate(), while other code can access the object without a check for instantiation through a call to the member function Global<TYPE, instance>::instance().
Explicit instantiation of the TYPE object is needed by defining a static or global object of type Global<TYPE, instance, CONVERTER> for each occurance of instance.
The default CONVERTER class GlobalConverterInt simply passes the constant instance directly to the constructor of TYPE. One other special CONVERTER class is defined: GlobalConverterVoid which calls the default constructor TYPE(void). User defined CONVERTER classes must define a public operator()(int) that converts the passed instance into the needed constructor parameter. The CONVERTER class may depend on instance (you may use a different CONVERTER class for each instance).
For example, if you want two global objects of type Fred, foo and bar. The instance foo should be created by calling the default constructor, while the instance bar should be created by calling a constructor Fred("bar").
// File Fred.h class Fred { public: Fred(void); Fred(char const* label); // ... }; // File GlobalFred.h: #include <assert.h> #include "Fred.h" enum GlobalFredInstance { foo, bar }; class GlobalConverterBar { public: char const* operator()(int instance) { assert(instance == bar); return "bar"; } }; // File GlobalFred.cc: #include <libcw/global.h> #include "GlobalFred.h" // Explicit instantiation: namespace { static Global<Fred, foo, GlobalConverterVoid> S_dummy_1; static Global<Fred, bar, GlobalConverterBar> S_dummy_2; }
Then inside a the constructors of (other) global classes use
Global<Fred, foo, GlobalConverterVoid>::instantiate() and
Global<Fred, bar, GlobalConverterBar>::instantiate()
While elsewhere use
Global<Fred, foo>::instance() and
Global<Fred, bar>::instance()
Note that there is no need to specify the converter class here as it won't be used anyway.
Consider
struct Fred { int a; int add(int i) { return a + i; } Fred(void) : a(5) { } };
Then the assembly code of Global<Fred, 0, GlobalConverterVoid>::instantiate().add(3) when compiling with -O3 looks like
cmpb $0,_Q35libcw10_internal_t10GlobalBase2Z4Fredi0.initialized jne .L3205 call initialize_instance___t6Global3Z4Fredi0Z19GlobalConverterVoid .L3205: movl _Q35libcw10_internal_t10GlobalBase2Z4Fredi0.instance_,%eax addl $3,%eax
While Global<Fred, 0>::instance().add(3) results in the assembly code
movl _Q35libcw10_internal_t10GlobalBase2Z4Fredi0.instance_,%eax addl $3,%eax
The latter is as fast as it can get (the same as when using a real static object).
Gamma et al, in their book Design Patterns, put the emphasis on the characteristic of a singleton class that it can have only one instance. That this is misleading, not to say incorrect, shows from the fact that they list as point four in the paragraph Consequences that a singleton pattern permits a variable (read controlable) number of instances. What in fact is the most important characteristic of a singleton is that all data of the singleton class and that includes static data, is encapsulated and can only be accessed through a single, and thus global, point of access.
If, for example, class Foo were a singleton:
class Foo { static int x; int y; private: Foo(void); Foo(Foo const&); ~Foo(); public: Foo& instance(void); };
then the number of x instances would always equal the number of y instances, and hence there is no reason to make x static.
There seems to be confusion about whether or not a singleton should have a private constructor. Also Design Patterns uses a protected constructor in their examples. This is only correct however when the singleton base class does not contain any static data, or when all data is private (as opposed to protected), the class provides it's own global point of access (the instance() function) and none of the derived classes has a public constructor. The latter requirement is the one that makes it dangerous to use a protected constructor for singleton base classes of libraries.
Especially with the `Global<>' implementation of libcw it is crucial that the constructors are private because the static instance_ pointer is not a member of the singleton class itself: every class derived from a `singleton' will have its own instance but still share the static data of the base class and therefore would not be a singleton anymore! Base classes without any static data however can use protected constructors if such is desirable.
It is interesting to note that for a class Atreyu that doesn't contain static data, every Global<Atreyu, instance> is a singleton on its own: it provides a global point of access to the data of class Atreyu. That for different values of instance more instances of Atreyu are created is irrelevant (as long as the constructors of Atreyu are private/protected of course).
However, it would not be safe to assume that a user class doesn't contain static data (sometimes it is even necessary that a class contains static data despite the fact that it is a singleton, as is the case for the libcw SignalServer class). And thus we arrive at the following restrictions for a true singleton class SINGLETON:
Libcw reserves -1 for the instance of singletons with exactly one instance.
As an example, lets consider a class Twin that should have two instances: left and right. Note that these two instances do share their static data members!
This class could be defined as
enum { left, right }; class Twin { friend class Global<Twin, left, GlobalConverterVoid>::Instance; friend class Global<Twin, right, GlobalConverterVoid>::Instance; // ... private: Twin(void); ~Twin(); Twin(Twin const&) { } Twin& operator=(Twin const&) { return *this; } }; static Global<Twin, left> leftTwin; static Global<Twin, right> rightTwin;
And the two instances could be accessed through:
Global<Twin, left>::instance()
and
Global<Twin, right>::instance()
respectively. When class Twin doesn't have static members then both instances would be true singletons.
There are two approaches for singleton base classes:
Consider the following code:
class B1 { protected: // Base class without static data B1(void); }; class B2 : public B1 { private: // Base class with static data static int data; friend B3; B2(void); }; class B3 : public B2 { private: // Base class with static data (from B2) friend Final; B3(void); }; class Final : public B3 { private: // The final singleton class friend class Global<Final, 1, GlobalConverterVoid>::Instance; // Only allow instance `1' Final(void); ~Final(); Final(Final const&) { } };
template<class FINAL, class CHILD = FINAL> class Singleton
This template is a simple Global adaptor class for singletons with exactly one instance. FINAL is the final singleton class while CHILD is the class that is directly derived from Singleton.
FINAL& Singleton::instantiate(void)
Returns a reference to the global instance of FINAL. If the instance wasn't initialized yet then it is first initialized. Use this function only in the constructors of other global objects.
FINAL& Singleton::instance(void)
Returns a reference to the global instance of FINAL. This function should be used outside constructors of global objects.
typename Singleton::Instance
The type of the friend class that has to be added to the FINAL singleton class.
template<class FINAL> class SingletonInstance
The type of the singleton instance place holder.
For example,
template<class FINAL> class B1 : public Singleton<FINAL, B1> { protected: // Base class without static data B1(void); }; template<class FINAL, class CHILD = FINAL> class B2 : public B1<FINAL> { friend CHILD; private: // Base class with static data static int data; B2(void); }; template<class FINAL, class CHILD = FINAL> class B3 : public B2<FINAL, B3> { friend CHILD; private: // Base class with static data (from B2) B3(void); };
// Final.h:
class Final : public B3<Final> { friend class Instance; private: // The final singleton class Final(void); ~Final(); Final(Final const&) { } };
// Final.cc:
static SingletonInstance<Final> Final_instance __attribute__ ((unused));
When a base class is very large, it would be inefficient to compile it as a template for each compilation unit that uses the singleton. This paragraph gives an example of how to deal with that dilemma.
This trick only works when the base class does not contain any static data members! Consider the following class Large, which does not have any static data:
// Class does not contain any static data (also not in member functions!) class Large { public: Large(void); // ... };
Then the following, small declaration is a true singleton:
class Final : public Singleton<Final>, public Large { friend class Instance; private: Final(void) { } };
because there is no way you can touch the data of the (single) instance of Final otherwise than through a call to Final::instance().
This trick is used for timer_event_server_ct and for SignalServer; it might be instructive to study those two classes.