eyt*
Jan 29, 2005

The Multiple Personalities of the Singleton

The Singletongof Design Pattern is by far one of most popular and most used design patterns, but the pattern is mostly associated with only one of its many uses, viz. the pattern’s intent, providing a single instance of a class from one global access point (and of course, the double check locking pattern, or a safe adaptation thereof, for multithreaded code).  In a recent discussion on a Patterns mailing list, the topic of destroying a Singleton object came up, and it is interesting various answers that came up during the thread.  This question is more involved that it first appears, and the problem is less about the mechanism of destroying the object, but instead surrounds the type of Singleton that we are talking about.

Unfortunately this problem is not really discussed in literature.  The only place that I am aware that really has a good modern treatment of the issue is Modern C++ Design, whereby Andrei says, “From a client’s standpoint, the Singleton object owns itself. There is no special client step for creating the singleton. Consequently, the Singleton object is responsible for creating and destroying itself. Managing a singleton’s lifetime causes the most implementation headaches.”

One of the approaches recommended in the thread of the aforementioned post was to simply do nothing. Baring in mind that in languages such as C++, Singletons are generally created on the heap, some may concern themselves about a memory leak, But as noted in Effective C++ Item 10, there is no memory leak in this approach, since to have a memory leak, you must lose a reference to memory, but this is not going to happen, and instead, most modern operating systems, memory utilized by an application is returned to the operating system when the application is terminated.

Instead, this approach has a worst problem, in that there is probably a resource leak. For example, a printer queue Singleton will likely have some communication link with a printer or other distributed printer queues or clients. By exiting without properly closing those connections, it is possible to have global leaks throughout your network. It is likely that that you will have to develop software to guard yourself against such problems in the unlikely case that a hardware or software problem occurs, but such problems should be exceptional and not the norm. In the cut and dry case of opening an application to display the contents of a print queue, you do not want such problems on your network.

If you are using a language like C++, one technique recommended in More Effective C++ Item 26 is what Andrei refers to as the Meyers Singleton, whereby you leave the languages do the construction only when it is required and let the language do the destruction at the end of the language.  An example of this technique follows:

Singleton& Singleton::getInstance() {
   static Singleton obj;
   return obj;
}

As explained in detail in the two aforementioned resources, the objects are destroyed in a LIFO manner, which is invoked by the little know function, atexit().

There are three unmistakable issues with this approach. First, dependencies on other Meyers Singletons can become problematic, as Andrei describes, since the order of construction and destruction can lead to undefined behaviour. The example that Andrei provides is the one where the Logger is only initialized when there is an error, and it emits an error on the Display. For the sake of this example, the Keyboard is instantiated first and then the Display, and during instantiation, the Display experiences an error, resulting in the Logger being instantiated. When you exit the application, these Singleton objects are destructed in the opposite order that they were constructed, thus the Logger is destructed first, then the Display, and finally the Keyboard, but for our example, let's say that the Keyboard experiences an error on destruction. What now? If the Keyboard accesses the Logger, it is now uses a Dead Reference that has been destroyed, which leads to undefined behaviour. As there is no way in C++ to define these types of dependencies amongst static variables, the solution that Andrei puts forth is to track the destruction of the object via a boolean variable, and therefore behave slightly more definably, but it is still not ideal. An alternative that Andrei describes in Modern C++ Design is what he refers to as the Phoenix Singleton, in which when it detects that it has died, the object is recreated using placement new, and destroyed again by using atexit() (with a few library implementation caveats here, since the behaviour is undefined for this case).

The second is that the type of singleton must be know a priori; for example, per se that your Singleton represents access to a configuration file. While today you may only be interested in a file on disk, it is possible that in the future you will have requirements that the data will be stored in a database, in another format (such as XML), or remotely using some middleware (such as CORBA). The above approach would require you to make this decision at design time, not runtime.

The other issue is with the lifetime. While we have delayed the construction of the object, the object destruction is still at the end of the application. Depending on the genre of Singleton, this could be a desired attributed, but it is important to distinguish the difference between the application’s lifetime and the Singleton’s.

In my last sentence, I may have struck a nerve with some, but it is important to note that the GOF book provides several consequences to choosing a Singleton design pattern. Most people are really comfortable with the first one, which is to provide access to a sole, unique instance. This means that during the lifetime of an application, any call to getInstance() returns the same object, at the discretion of the Singleton object.  In other words, the Singleton can decide how and when clients will use the object.

The second consequence is that it can clean up the global namespace; in particular you do not have global data or methods, and instead just have a simple interface to acquire an instance to the class. This may not be as important today as it was originally, with concepts like packages and better compiler support for namespaces (The C++ ISO Standards committee voted namespaces into the language in July 1993, per The Design and Evolution of C++).

The third consequence is that the Singleton pattern lends itself nicely to refinement of operations and representations. In other words, the client using a Singleton class is interested in performing a function; they are not particularly interested in how the class is implemented. For example, the Configuration File example I previously mentioned fits this nicely. The user is not particularly interested in whether it is a File, Database, or accesses the file via middleware. The import aspect is that it provides the functionality that the client uses and supports the contract set forth by the Singleton.

The fourth consequence is actually one that surprises many people, in that it states that the Singleton object can control the number of objects that are on a particular system. This can be used for many effects, such as returning a unique instance for each Thread in the system or something more like load balancing.

The fifth consequence essentially highlights the differences between the Singleton design pattern and the Monostate design pattern, in which the Singleton pattern provides more flexibility in implementation than the Monostate pattern.

Now that we have gone through the five consequences, it should be clear that the Singleton design pattern’s purpose is not to provide access to one object, but rather to control access to objects, centralizing this control, such that you can easily modify the implementation’s policy for accessing objects in a single place, the Singleton itself. For example, the Pool pattern from POSA3 could easily be a specialization of the Singleton pattern.

Obviously, the delete Singleton::getInstance(); is not the solution; in addition to the implementation of the Singleton object changing, of equal concern is the fact that the memory location may not be allocated by new. It may have be allocated by a placement new or a memory manager, whereby the deletion of the object via delete would be inappropriate.

John Vlissides, one of the GOF, wrote some portions of this in his Pattern Hatching and, in particular, To Kill a Singleton. This is an excellent read on the topic, although it is partly dependent on C++. Never-the-less, the first technique that John discusses resembles the Meyers Singleton, In it, he recommends allowing the language to recoup the memory by creating a class that holds a Singleton and is responsible for destroying it.  Of course, just as the Meyers Singleton, this scheme does not work when there are Singleton dependencies.  Furthermore, it also only works when the lifetime of the Singleton is the same as the lifetime of the application, which John indicates is normally the case.  In the Odds and Ends section of the column, John notes that the static system employed in these schemes is not thread-safe; since C++ does not yet have knowledge of threads, multiple threads can access the same static initializer at the same time, and no locking scheme can prevent this from occurring (See also the double check locking pattern).

In response to the dependency issue, John simply points out that an atexit() scheme (similar to Andre’s) may be the only way around this, although the difference between Andre”s scheme and John”s is that John is recommending a single object that takes care of deleting all the Singleton objects in an application, and that this object maintains the dependency information as required.  David L. Levine, Christopher D. Gill, and Doug Schmidt formalized this into the Object Manager Pattern [PDF], which aims to resolve many of the issues that have been issued above, not to fail to mention the issue with certain embedded systems and static initialization.

So what is the proper answer to how to destroy a Singleton?  The answer really varies based on your use and system architecture.  In a large application, the Object Manager should be used or at least considered.  For smaller applications, this can depend on what the Singleton really is, and how important it is for it to be destroyed.  But there is definitely more than meets to eye.

Filed In

Navigation

eyt*