To Kill A Singleton
"Pattern Hatching" column for June '96 issue
The Singleton pattern is remarkably simple. Its intent states, Ensure a class only has one instance, and provide a global point of access to it.
It's also flexible, as we saw last time when we applied it to our file system design. It helped us encapsulate the creation of
User objects, which give file system users the authority to access their own files and no one else's.
To obtain a
User object, a client program calls a static
This of course is just a gussied-up version of Singleton's static Instance operation. In the vanilla version of the pattern, Instance limits you to exactly one instance of the Singleton class (the
User class in this case).
But one of the pattern's consequences is the option to *control* the number of instances, as opposed to flatly precluding more than one. We availed ourselves of that option to preclude more than one
User instance *per user*. That way an application is free to create multiple instances if it caters to multiple users at once.
Another thing I mentioned last time was the pattern's deafening silence on the issue of deletion. Who deletes Singleton instances, and how, and when?
The words "delete" and "destructor" never appear in the pattern. Honest oversight? Conscious omission? Or perhaps evading a hard problem? In this article I hope to convince you that it's neither of the last two. We'll also look at an assortment of new observations on this surprisingly rich little pattern.
The All-Important Destructor
Like any self-respecting class, a Singleton class should define a destructor. If Singleton is to be subclassed, then the destructor should be declared virtual. All just C++ 101.
Now comes the tricky part: Should this destructor be public, private, or protected?
"What's the issue?" you might ask. "Make it public and be done with it." The implication here is that singleton destruction is *explicit* - that is, it's a client responsibility. But there's a reasonable argument against that.
The Singleton pattern places responsibility for object *creation* squarely in the Singleton class. Clients go to this class to get a singleton instance. So if a client *deletes* the instance without the Singleton class knowing about it, from then on the Singleton class will hand out "dangling references," which point to objects that no longer exist.
Singleton's responsibilities imply that it *owns* the instance it creates; and ownership, finally, means responsibility for deletion. This is in contrast to other creational patterns, such as Abstract Factory and Factory Method, that do not retain ownership of the instances they create.
That said, we might still get away with a public destructor *if* the following are true: * The destructor deletes and cleans up its references to the static instance. Then a subsequent call to the Instance operation will work as it did the first time.
Clients do not retain references to the Singleton object. Otherwise, they would be left with dangling references. These restrictions are stringent enough to make explicit destruction the exception rather than the rule. In our file system design, for example, we have to consider how and when
User objects get deleted. Suppose we let clients delete
User objects explicitly with the normal delete operator. We could be even more explicit and provide a static
LogOut operation that mirrors the
LogIn operation (either way; the deletion interface doesn't really matter).
Currently there's no way for the
User class - a kind of Singleton - to know which clients have references to
User objects. So if a
User gets deleted, clients will end up with dangling references. Thoroughly unacceptable.
While we probably need a mechanism for logging users out, say, for bookkeeping purposes, the potential for dangling references tells us that we can't use deletion as the log-out mechanism. In other words, we shouldn't confuse logging out with deleting a
Whatever interface we choose for logging a user out, it cannot involve explicit destruction of a
User object. The point of all this is to make the case for rejecting the public destructor. Rejecting a private destructor is much easier: we want to allow subclassing the Singleton class, after all. That leaves only one choice - a protected destructor. Back now to square one: How does a Singleton ever get deleted?
The thing about Singleton objects is that they are usually, if not inherently, long-lived. Often they exist for the life of the program. You delete them not so much to reclaim space but to shut down in an orderly manner. You want to close files, unlock resources, terminate network connections, and so forth, without the appearance of an abrupt termination (read "crash").
If your Singleton objects ever need to do such cleanup, they probably have to wait until just before the program terminates. This is a nice property, because it means C++ may be able to do the deletion for us implicitly. C++ deletes static objects automatically at program termination.
The language guarantees that their destructors will be called and space reclaimed at that time, although it doesn't guarantee the calling order. For now let's assume that ordering isn't a problem; there is only one singleton in the program, or if there is more than one, their destruction is not order-dependent. Now we can define the Singleton class like this:
where SingletonDestroyer is a class whose sole purpose is the destruction of a particular Singleton object:
The Singleton class declares a static SingletonDestroyer member, which gets created automatically at program startup. If and when the user first calls Singleton::Instance, not only will the Singleton object get created, but Instance will also pass that object to the static SingletonDestroyer object, effectively transferring ownership to the SingletonDestroyer.
When the program exits, the SingletonDestroyer will be destroyed, and the Singleton object with it. Singleton destruction is now implicit. Simple — almost.
Note the *friend* declaration in the declaration of the Singleton class. It's needed to give the destroyer access to Singleton's protected destructor. Not pretty if you have an aversion to the *friend* keyword, but it's necessary, given the earlier arguments against a public destructor.
And it exemplifies what is perhaps the most defensible use of *friend*: to define another level of protection, as opposed to providing a work-around for a bad design. To maximize reuse, especially if there are multiple kinds of singletons in your program, you might save yourself some typing and define a templatized Destroyer class:
That lets us define Singleton like this:
There are two potential problems with implicit destruction. First, it doesn't help you if you need to delete your singleton *before* the end of the program. In that case, it's hard to imagine an approach that doesn't require explicit destruction.
Moreover, you'll either have to add mechanism (say, reference-counting) to minimize the dangling reference problem, or you'll have to force clients to access the Singleton instance exclusively through the Singleton::Instance operation.
- One way to do the latter involves making Instance return a reference to a Singleton
- and disallowing copy and initialization by declaring the assignment and copy constructors private:
This approach isn't foolproof, unfortunately, since a client can always take the address of the value that Instance returns, or it can cast away these safeguards entirely. Nevertheless, this issue is unlikely to be a show-stopper.
As I pointed out earlier, the Singleton pattern favors long-lived objects. So explicit deletion probably won't be a common problem. The second and thornier problem surfaces when you've got multiple singleton objects in your program, and they depend on each other.
In that case, the order of destruction might be significant.Consider our file system design, where we've applied the Singleton pattern twice. We used it first to control the number of
User objects, which produced the singleton
The second application ensured just one Grouping object, which defines a mapping between users and the groups they belong to. The Grouping object lets us define protection for groups of users rather than just individuals. Because it doesn't make sense (it's downright dangerous, in fact) to have more than one grouping active at once, we made the Grouping class a Singleton.
A Grouping object maintains references both to
User objects and to Group objects. It doesn't own the
User objects, but it could conceivably own the Group objects. Either way, it seems desirable to delete the Grouping object before the
User objects, just on principle. True, the dangling references that would otherwise result probably won't be a problem, since Grouping shouldn't have to dereference any of them during its destruction — but then again, you never know.
My point is this: the destroyer approach, based as it is on an unspecified language implementation mechanism, begins to fray when destruction order is important. If your application calls for multiple, dependent singletons, then you may have to revert to explicit destruction.
One thing's for sure: you can't use more than one destroyer if the singleton destructors depend on one another. An alternative is to eschew destroyers altogether and rely instead on the draft-standard
atexit() function, as Tim Peierls suggested to me: I maintain that
atexit() is a good way to cleanup singletons in C++ when you really want single instances with program lifetime and no replacement.
The draft standard promises a lot: The function
atexit() from can be used to specify a function to be called at exit. If
atexit() is to be called, the implementation shall not destroy objects initialized before an
atexit() call until after the function specified in the
atexit() call has been called.
The only way I can see this failing is if a statically initialized object whose destructor depends on a Singleton instance is initialized *after* the Singleton instance is constructed, i.e., through some other static initialization. This suggests that classes having static instances should avoid depending on singletons during destruction. (Or at least there should be a way for such classes to check for the existence of the Singleton during destruction.)
While this obviates the need for destroyers, the real problem — deleting mutually-dependent singletons — remains. Garbage collection, anyone?
Odds And Sods
Long ago, in a galaxy far, far away, Scott Meyers 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:
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. 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 noticed a more fundamental problem with this approach: It turns out that it is not possible to make 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 got bitten by this very bug: The Double-Check pattern 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, Doug sent me a follow-up:
One 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 and also enables easy parameterization of the LOCK strategy. I've enclosed it below, just for fun.
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: 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:
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 again — I love it!). For example:
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!