6.11 Working with SingletonHolder
The SingletonHolder class template does not provide application-specific functionality. It merely provides Singleton-specific services over another class—T in the code in this chapter. We call T the client class.
The client class must take all the precautionary measures against unattended construction and destruction: The default constructor, the copy constructor, the assignment operator, the destructor, and the address-of operator should be made private.
If you take these protective measures, you also have to grant friendship to the Creator policy class that you use. These protective measures and the friend declaration are all the changes needed for a class to work with SingletonHolder. Note that these changes are optional and constitute a trade-off between the inconvenience of touching existing code and the risk of spurious instances.
The design decisions concerning a specific Singleton implementation usually are reflected in a type definition like the following one. Just as you would pass flags and options when calling some function, so you pass flags to the type definition selecting the desired behavior.
class A { ... };
typedef SingletonHolder<A, CreateUsingNew> SingleA;
// from here on you use SingleA::Instance()
Providing a singleton that returns an object of a derived class is as simple as changing the Creator policy class:
class A { ... };
class Derived : public A { ... };
template <class T> struct MyCreator : public CreateUsingNew<T>
{
static T* Create()
{
return new Derived;
}
};
typedef SingletonHolder<A, StaticAllocator, MyCreator> SingleA;
Similarly, you can provide parameters to the constructor or use a different allocation strategy. You can tweak Singleton along each policy. This way you can largely customize Singleton, while still benefiting from default behavior when you want to.
The SingletonWithLongevity policy class relies on you to define a namespace-level function GetLongevity. The definition would look something like this:
inline unsigned int GetLongevity(A*) { return 5; }
This is needed only if you use SingletonWithLongevity in the type definition of SingleA.
The intricate KDL problem has driven our tentative implementations. Here's the KDL problem solved by making use of the Singleton class template. Of course, these definitions go to their appropriate header files.
class KeyboardImpl { ... };
class DisplayImpl { ... };
class LogImpl { ... };
...
inline unsigned int GetLongevity(KeyboardImpl*) { return 1; }
inline unsigned int GetLongevity(DisplayImpl*) { return 1; }
// The log has greater longevity
inline unsigned int GetLongevity(LogImpl*) { return 2; }
typedef SingletonHolder<KeyboardImpl, SingletonWithLongevity> Keyboard;
typedef SingletonHolder<DisplayImpl, SingletonWithLongevity> Display;
typedef SingletonHolder<LogImpl, SingletonWithLongevity> Log;
It's a rather easy-to-grasp and self-documenting solution, given the complexity of the problem.
|