Citation :
Long ago, in a galaxy far, far away, Scott Meyers[endnote1] posited the
following:
My [version of Singleton] is quite similar to yours, but instead
of using a class static and having Instance return a pointer, I
use a function static and return a reference:
Singleton& Singleton::Instance () {
static Singleton s;
return s;
}
This seems to offer every advantage your solution does (no
construction if never used, no dependence on initialization order
between translation units, etc.), plus it allows users to use
object syntax instead of pointer syntax. Furthermore, my solution
makes it much less likely a caller will inadvertently delete the
singleton in a misguided attempt to avoid a memory leak.
Am I overlooking a reason for returning a pointer to a class
static instead of a reference to a function static?
The only drawback I could see here is that it makes it hard to extend
the Singleton through subclassing, since Instance will always create
an object of type Singleton. (For more on extending the Singleton
class, see the discussion beginning on page 130 in *Design Patterns*.)
Besides, one needn't worry about deleting the Singleton instance if
its destructor isn't public. While I have since developed a slight
preference for returning a reference, in the end it seems to make
little difference.
Later, Erich Gamma[endnote2] noticed a more fundamental problem with
this approach:
It turns out that it is not possible to make [Scott's approach]
thread-safe if multiple threads can call Instance. The problem is
that [some C++ compilers generate] some internal data structures that
cannot be protected by locks. In such situations you would have to
acquire the lock at the call site---pretty ugly.
Yep. And it wouldn't be long before Doug Schmidt[endnote3] got
bitten by this very bug:
[The Double-Check] pattern [Editor's Corner, March '96] emerged as
I was proofreading John Vlissides' Pattern Hatching column for
April '96. In this column, John talks about Singleton in the
context of protection for multi-user file systems. Ironically,
we'd been having some mysterious problems recently with memory
leaks on multi-threaded versions of ACE on multi-processors.
As I read John's column, it suddenly struck me that the problem
was caused by multiply initialized Singletons due to race
conditions. Once I saw the connection between the two issues, and
factored in the key forces (e.g., no locking overhead for normal
use of the Singleton), the solution jumped right out.
About a month later[endnote4], Doug sent me a follow-up:
[O]ne of my grad students (Tim Harrison) recently implemented a
C++ library class called Singleton, which basically adapts
existing classes to become "Singletons." We use this in ACE now,
and it's quasi-useful. The nice thing about it is that it
automates the Double-Check [pattern][endnote5] and also enables easy
parameterization of the LOCK strategy. I've enclosed it below,
just for fun.
template <class TYPE, class LOCK>
class Singleton {
public:
static TYPE* Instance();
protected:
static TYPE* _instance;
static LOCK _lock;
};
template <class TYPE, class LOCK>
TYPE* Singleton<TYPE, LOCK>::Instance () {
// perform the Double-Check pattern...
if (_instance == 0) {
Guard<LOCK> monitor(_lock);
if (_instance == 0) _instance = new TYPE;
}
return _instance;
}
I was intrigued, especially the part about being "quasi-useful." I
asked whether he said that because this approach doesn't really preclude
creating multiple objects of the base type (since presumably that type
is defined as a non-singleton elsewhere). His response was
illuminating, and a little unnerving[endnote6]:
Right, exactly. Another problem is that many C++ compilers (e.g,.
G++) don't implement static data members within templates. In
this case you have to implement the static instance method like
this:
template <class TYPE, class LOCK> TYPE* Singleton<TYPE, LOCK>::Instance () {
static TYPE* _instance = 0;
static LOCK _lock;
if (_instance == 0)
// ...
return _instance;
}
Ah, the joys of cross-platform C++ portability! ;-)
That, in turn, fired some of my dormant neurons. I wrote back that if
you really wanted to make a class inherently singleton, you could
subclass it from this template, passing the subclass as a template
parameter (Cope's curiously recurring template pattern[endnote7]
again---I love it!). For example:
class User : public Singleton<User, Mutex> { ... }
That way, you would preserve Singleton semantics without recoding the
pattern in all its multi-threaded gory (sic).
Caution: I haven't tried out this variation myself, nor have I had
occasion to use it. I just think it's neat, both aesthetically and in
the way we arrived at it. I used to think Singleton was one of the
more trivial of our patterns, hardly worthy to hobnob with the likes
of Composite, Visitor, etc.; and maybe that explains its silence on
some issues. Boy, was I wrong!
|