A Unified Singleton Framework

A Unified Singleton Framework

By Jeff Daudel

Overload, 11(56):, August 2003


Introduction

Software systems often contain objects that exist for the duration of the program. A relatively small system may have only a handful of these objects, where a large system could have hundreds. At first glance, maintaining these objects, including their creation and destruction order, may not seem too difficult. However, closer inspection reveals the true magnitude of the complexity involved. Only ten such objects have over 3.6 million different orderings; one hundred objects have a number of orderings that is represented with 157 zeros. Now consider periodically adding or modifying the relationship of these objects. In a system where core systems and even pieces in those systems are built with these objects, it would not be unreasonable for a system to have many ongoing changes, even if it were welldesigned. Accounting for all these factors, maintenance of both the proper orderings and all the dependencies can quickly become a nightmare.

Many developers attempt to handle these objects by hand and encounter difficulties. Others conclude that large C++ systems cannot be written without automated garbage collection, and I would say that the belief has a lot of credit given the numbers they are up against. But garbage collection can be costly. The run-time and memory overhead, coupled with its complexity, make it unacceptable for many applications. And I would have to ask, "Is this the best we can do to handle such a critical problem?"

Additionally, let me add that this may not even be an isolated problem, but only one of several unaddressed issues of a common design pattern. Similar problems exist and are more prevalent as a system grows. This design pattern is a powerful tool to build complex C++ systems, but it also might have a few gaping holes in its support. The pattern I am referring to is the singleton. I believe it is one of the most fundamental patterns for a software foundation. If a system consisted purely of objects, singletons might be the only reason to have the word "the" in a programmer's vocabulary. Anyone that ever said, "the manager", "the renderer", or "the startup state" would be referring to a specific singleton object. But just because something has a lot of potential, does not mean a gloomier side doesn't exist. Unfortunately in software, the gloomier side is usually in the implementation details. The singleton pattern is no exception.

In this paper, I will outline the classical singleton pattern in more detail. I will present a generic system that will not only provide the basic capabilities of the singleton pattern, but will provide several other useful abilities. It will extend the notion beyond the current scope of a singleton to provide answers in a larger context of problems. I will discuss the common problems that occur when implementing the pattern in the C++ language. I will then describe how the system attempts to solve all the issues, even on massive scales.

More specifically, the new system will deal with:

  1. Integrating two different families of singletons - static and dynamic

  2. Handling massive numbers of dynamic dependencies

  3. Deducing a valid destruction order for all singletons

  4. Providing robustness by detecting cyclic dependencies and invalid uses of the system

These benefits, along with several others, will be provided to the programmer at a cost of usually two lines per object.

These capabilities form "A Unified Singleton Framework". It attempts to unify common problems of all such objects. It unifies different classifications of singletons. It unifies their solutions, relieving the usual hand crafting on an object by object basis. Once I have demonstrated this system to you, I hope you will agree that you would not want to write another system without using the unified singleton approach.

The Classic Singleton

The first book to formally document the singleton was the Design Patterns book by Gamma et al. The book describes singletons as objects that have two properties - global access and single instance. Let's examine each in turn.

Global access could be another term for "convenient access". The benefit of easily accessing an object, such as not having to pass a parameter through a multitude of functions, is less code and less up-front planning. This together means simpler. One downside to a singleton being globally accessible, is that it conflicts with the principle of encapsulation. If there were a way to make an object conveniently accessible but only selectively available to its intended audience, there would be room for improvement on the pattern. But current methods of restricting access encroach on convenience, which would void the primary reason in the first place.

The second principle assures the use of the same object. Many assumptions and synchronization requirements rely on this singularity, and the singleton pattern delivers this well.

Singleton Usage

Consumers of singletons really care about one usage feature - getting a singleton. They need not concern themselves when singletons are created or destroyed, or the dependent relationship between one singleton and another. Nor do they care about where they came from, where they live, or what parameters were needed to create them. They proclaim, "Give me! No Details Please!" I will demonstrate this in code shortly.

It is this "getting" that provides the simple interface into the singleton abstraction. The following is an interface for singletons which covers all possible singleton classes. It is a templated function that takes the singleton class as a type parameter.

template<class TSingleton>
TSingleton* GetSingleton();

Using the interface, an example of singleton usage would be:

GetSingleton<Printer>()->
      Print(GetSingleton<DocManager>()->
      GetMyDocument());

This shows two singletons, Printer and DocManager , being accessed in the same line. No previous preparations were made. No objects had to be passed in. This is especially useful for the main function, since no objects are ever passed in.

Making a Singleton

How difficult is it to make a class a singleton? This is a good question since many highly regarded books have differing opinions. Effective C++ by Scott Meyers, and the Design Patterns book present slightly modified versions based on what appear to be a similar theme. You might have to write a halfdozen or more lines of cookie-cutter code using a static variable of some sort:

class Singleton {
public:
  static Singleton& GetSingleton() {
    static Singleton instance;
    return(instance);
  }
private:
  Singleton() {}
};
Singleton Singleton::instance;

Modern C++ Design by Andrei Alexandrescu takes a step forward in providing some templated classes to alleviate most of this work, but there are still several template decisions and instantiations to make. He also presents several usage modifications that can be applied to individual singletons. This is important given there are probably innumerable variations of problems. But what is equally important is to allow a system to deal with these pieces in a unified manner. This will be demonstrated later on.

If the previous code seems like a lot, that's because it is. There is a simpler way, and it can be done in two additional lines:

class log {
public:
  API_SINGLETON(log);
};
// In a .cpp file
DEFINE_SINGLETON(log_impl);

That's it. log is now a singleton. Any function can retrieve the singleton by making the call:

GetSingleton<log>();

The function will retrieve the one and only instance of log . The log_impl class can be the log class itself or any derived type. This would allow for polymorphic singletons and their implementations to be encapsulated from the user. The singleton will be properly created by the time it is needed and destroyed before the program exits. No further work is needed and no singleton memory leaks are assured.

The following is a simplified version of the API_SINGLETON macro. I've temporarily stripped out the notion of multiple types of singletons, linkages and private data:

#define API_SINGLETON(T)\
class SingletonTraits {\
public:\
  SingletonTraits();\
  ~SingletonTraits();\
  static T* mpInstance;\
};\
SingletonTraits SingletonTraitsInstance

This macro declares an internal class traits type and then makes an instance of that trait.

You might wonder why not use a templated traits class instead of a macro. It is a technical issue about what can be templated and what cannot. I will point out later in the article that a unified singleton can be shared across component boundaries. On some platforms, such as Microsoft's Windows, certain linkage specifiers cannot be templated. One component is required to export the definition while another component will need to import it. There is some leeway for member functions, but not for member data. Although not my preference, macros seem like the only way around this issue. But that's ok. The macro/template combination provides a powerful idiom. The DEFINE_SINGLETON() macro has requirements that cannot be templated either.

This trait instance allows the system to detect whenever the class is constructed or destroyed, by monitoring the respective methods. Because of this, the system is always aware if more than one singleton is created, or if one is improperly destroyed. It will immediately notify the user of any invalid usage, making it difficult to accidentally subvert the system. It will be shown later that some singletons need to be made by the user at least once, so we cannot protect the constructors or destructors for their implementation type.

For example, if a user attempts to make a singleton class on the stack, the system will report an assertion. If a user attempts to delete the singleton by calling delete, an assertion is reported. There isn't a lot of room for misuse, which means less time tracking down errors.

The DEFINE_SINGLETON() macro is a bit more involved. It implements several features that are not present in previous singleton frameworks. I would like to outline their functionality first before discussing it further.

With these traits, a simplified version of the GetSingleton template looks like this:

template<class T>
inline T* GetSingleton() {
  typedef typename T::singleton_traits traits;
  if(traits::mpInstance == NULL) {
    Create< T >();
  }
  return(traits::mpInstance);
}

Most of the difficult work is deferred to the Create() method which is defined by the DEFINE_SINGLETON macro and explained later.

Dynamic Singletons

Let's take another look at a different singleton case. There is one case in particular that usually influences a programmer to reject singleton as a design for managing an object's lifetime.

Let's assume that a program uses a global object called the "Renderer". Let's also say that we want two different versions of the renderer - one for OpenGL and one for DirectX. They both utilize the same interface. We want to choose which renderer to use when the program starts. This means the selection of which object cannot be made until run-time. We also want to create the renderer with some run-time arguments.

This is where a programmer may now say, "Oh well. The renderer is sort-of a singleton, but just doesn't quite fit the pattern. I'll have do everything from scratch on this one and manage it by hand." Let's pause there.

This is the case where if something doesn't exactly fit one's conception of singleton, then it might not fit at all. But if the programmer were willing to slightly extend his notion of a singleton, there would be a lot more room to work with.

If you recall, the original usage of a singleton is quite simple and has only one requirement - "GetSingleton". If both renderers have the same interface, then the consumer does not care which version of the singleton he is getting. He wants the one and only "renderer". Somebody else should have already taken care of selecting which version the renderer represents underneath.

This is an example of a dynamic singleton. As far as the consumer can tell, it is no different from a static singleton, which is bound during compile-time. Consumers just say "get" and they expect a singleton. Who bound it, created it, or anything else is remains unnecessary detail to them.

A dynamic singleton is just as easy to make:

class Renderer {
public:
  API_DYNAMIC_SINGLETON(Renderer);
};
// in the .cpp file
DEFINE_DYNAMIC_SINGLETON(Renderer);

Functions can get the singleton by making the same call:

GetSingleton<Renderer>();

The one instance of the renderer will be returned. It will either be the OpenGL or the DirectX version, depending on the setting code.

The dynamic singleton leads to other benefits as well. It can be set by modules that are loaded later in the program, also called latebinding. They can also be swapped out for another singleton. For example, it is possible to swap the OpenGL renderer for the DirectX version during the middle of the program execution. If all the consumers use the Renderer interface, they will never be aware of the difference, allowing for a lot of flexibility. If a singleton must be created with run-time evaluated arguments, dynamic singletons should be your choice. In contrast, static singletons are always ready, so there really is no appropriate time to provide constructor arguments.

You might think that this violates the principle of single instance. From a user's point of view, there must be only one instance of a singleton at any point in time. And the unified system assures this. But deleting a current singleton and then immediately creating a new one to replace it, would not imply multiple instances at any frame in time. Getting the singleton will never be ambiguous, which as pointed out previously, is the fundamental requirement of any singleton framework.

There is a slight extension to the interface of a dynamic singleton. We need to allow the singleton-producer code to select which singleton to use, and we do this through SetSingleton:

template<typename TInterface,
         typename TImplentation>
SetSingleton(TImplementation* pSingleton);

This method will set the singleton pointer. Any function that calls GetSingleton() on a dynamic singleton that has not been set would be undefined. Notice there is a second templated argument - TImplementation . This type is used as the derived type when deleting - which means the interface need not expose a public destructor, and the implementation is kept encapsulated from the consumer. For trivial classes, the implementation could be the same type as the interface. When a dynamic singleton is destroyed, it will be deleted properly.

Contrast this with a static singleton. Static singletons are always set, and do not support the SetSingleton() interface. If you were to call SetSingleton() on one, the compiler would report a syntax error. One big advantage of static singletons is that you can get them during global initialization as well, which occurs before main is invoked.

Destruction and Dependencies

Now that all superficial interface discussion has been touched upon, I can discuss the real bread-and-butter of a unified singleton framework. It solves a problem that is more devious than most engineers may at first imagine, or at least until they actually want their program to shutdown without making a mess.

As a project's lifetime progresses, developers are usually capable of getting a program to boot up and eventually load up all the objects they will need. But, getting the program to shutdown and actually delete all those objects is usually another matter. The complexities of preventing one object being destroyed too early (before another that it depends upon), or of avoiding an unanticipated cyclic dependency during the development, are often realized too late. A program may not be able to shut itself down cleanly without crashing and might have to rely on the operating system to abruptly kill the process.

The solution to this problem lies in well-defined dependencies. If the singleton framework knows the proper dependencies, then this problem is easier to solve.

Let's say there are a keyboard, a log and a disk . The keyboard writes to the log . The log in turn writes to the disk . The keyboard depends on the log , and the log depends on the disk . It would look like this:

keyboard -> log
log -> disk

There is only one proper destruction order if a dependee were to always be available to its dependent:

  1. Destroy keyboard (no object depends on it)

  2. Destroy log ( keyboard is already destroyed)

  3. Destroy disk ( keyboard and log are already destroyed)

In Modern C++ Design, Andrei Alexandrescu suggests that a programmer could manually assign longevity numbers to singletons to convey these dependencies. This is certainly plausible if there were only a few objects, but what about a dozen? Longevity numbers themselves have no inherent meaning; they are only relative to other singletons. As a system develops, some of those singletons will change. And if you recall, ten objects already have 3.6 million different longevity orders. If a programmer were able to continually consider 1,000 of those, there would still a few million he was omitting. This is an intractable problem. One thing I learned in my computer science classes, I could spend my time more wisely than trying to solve an intractable problem.

You might be tempted to think that finding an order for ten really isn't that hard. If there are only a couple dependencies between singletons, finding one usually isn't. But every time a new dependency is introduced, the entire order needs to be reevaluated and could change. As the system grows and more code needs to get shared, the dependencies will get complex. That is when the magnitude of this problem usually shows up.

Andrei does suggest an alternative by alluding to a dependency manager for singletons. But he also brings up a separate optimization issue in that singletons that are not explicitly used should not be created. The overhead of unused singletons could be inefficient. Because of this, references must be placed in the dependees that refer to the dependents. Using the above example, the disk would have to refer to the log . The relationships would then look like this:

log->disk
disk->log

This would cause a circular dependency. Due to this, Andrei dismisses the idea of singleton dependencies altogether.

I would first have to examine the reasoning for such an optimization. Most singletons that are at the system-level will eventually be required anyway and must be allowed for. Another difficulty is that one dependent may have multiple other dependents, forming a large dependency tree. Temporarily optimizing out a root singleton may prevent many other dependencies from being specified. It may not be possible to recover these at a later point in the program.

If a singleton's creation overhead is really significant, there are alternative solutions available. The overhead could be moved out of the singleton's constructor and into the first usage of the object, such as in its member methods. For example, in a log class, a file could be opened when the "logging" method is first executed. I would describe the benefit of this removal optimization as minimal compared to the enormous difficulty of obtaining a proper destruction order out of a million possibilities, or even a number represented with 157 zeros. To put this in different perspective, I wouldn't want to be distracted away from a taming a man-eating beast because a small mouse crossed my path.

The reason why correct dependencies will find a proper destruction order is that the unified singleton framework has a "Singleton Stack". Whenever an object is fully constructed, it is pushed on to the singleton stack. When the program exits, it will destroy the singleton stack in the reverse order it was created.

To specify a dependency, there is an additional line of code in the dependent's constructor:

disk::disk() {}
log::log() {
  GetSingleton<disk>();
}
keyboard::keyboard() {
  GetSingleton<log>();
}

A rule of thumb that has been helpful to me is that if you access another singleton in any member function (or the destructor), then you must get the singleton in the constructor as well. I call this the principle of "symmetric getting". The result will be correct dependencies.

A couple of others things to note. Static singletons are created during their first get. Singletons are not added to the singleton stack until their constructors have completed. So no matter which singleton is requested first - disk , log , keyboard , the singleton stack will always be the same - disk , keyboard , then log .

If log is asked for first, log will create disk and put it on the stack first. If keyboard is asked for first, it will create disk and then log and then keyboard . This is very similar to how constructors in derived objects work in C++. It represents the essence of dependencies.

Cyclic Dependencies

There is also one inherently invalid sequence of dependencies - cyclic dependencies. When cyclic dependencies are introduced, they eventually lead to an infinite loop. Thus they always need to be avoided. For example, if we had these dependencies:

log::log() {
  GetSingleton<disk>();
}
disk::disk() {
  GetSingleton<log>();
}

Which should be created first? Which should be destroyed first? There is no correct answer. But that's ok, since the unified singleton framework will detect cyclic singleton dependencies during run-time. An assertion will be encountered if one exists. This means that if no assertion is encountered, you are guaranteed to have a non-cyclic singleton design, which could otherwise be very difficult to prove in a large system. You can then rest easy as a maintainer of such a system. This also guarantees that there exists a correct destruction order, and the system will be able to invoke that order - another comforting thought.

Putting it all together

Now that I've touched upon the main issues that the unified singleton framework addresses, we can return to the Create method which is instanced by DEFINE_SINGLETON() macro.

At a high level, it implements the following:

  1. Creation code for the polymorphic implementation type.

  2. Registering the singleton onto the "singleton stack"

  3. Tracking all possible states a singleton may be in.

  4. Detecting misuse - cyclic dependencies, multiple runtime-bound singletons

Here is the Create method from the static singleton implementation:

template<typename T, typename TCreatePolicy,
         typename TThreadPolicy = BasicThreadPolicy>
class static_singleton_impl
      : public singleton_impl<T> {
  static void Create() {
    T*& instance = Instance();
    TState& state = State();
    if(state == Constructing) {
      // Cyclic construction was found
      ASSERT_MSG(0, "Cyclic
                         singleton dependency detected");
    }
    else if(state == Destroyed) {
      // Accessing a dead singleton
      ASSERT_MSG(0, "Accessing
      dead singleton");
    }
    else {
      // must be in uninitialized state
      ASSERT(state == Uninitialized);
      // set constructing state
      state = Constructing;
      instance = TCreatePolicy::Create();
      // after instance has completed
      // construction, register it on the
      // singleton stack
      state = Constructed;
      GetSingleton<SingletonStack>()->
                                Register(Destroy);
      // register an atexit call
      atexit( AtExit );
    }
  }
}

The method is primarily responsible for dealing with two variables - the instance and its state. It treats the singleton as a mini-state machine.

The first "if check" looks to see if the singleton is already being constructed. If so, then this singleton has a construction cycle. This can often be difficult to visually inspect, since singleton recursion may occur several layers deep.

The second "if check" looks to see if the singleton was previously destroyed. This would be a "dead singleton" access. The framework is responsible for ensuring that this does not happen; that is the benefit of the framework in the first place. It is more of an internal check to make sure the framework is working properly.

If the singleton passes all those tests, then the creation begins. It sets the state to Constructing and then invokes the creation policy, which may be customized to each singleton. A provided simple creation policy will call new on the implementation type, which might be a derived type or the type itself. After it is fully constructed, the state is set to Constructed and then registered with the SingletonStack . The SingletonStack is a singleton itself and will always be on the bottom of its own stack. The Destroy parameter is a static class member that ensures smooth destruction of the singleton in a similar fashion to how it was created.

An AtExit handler is then registered with the C run-time system. It will detect when a module is being shutdown. It is worth noting that the atexit must be called in singleton template implementation and not in the SingletonStack implementation. There can be several atexit stacks when there are multiple run-time modules. We want to ensure that a singleton's atexit is registered in the module that was responsible for creating the singleton.

A dynamic singleton implementation does not have a related Create method, since it never actually creates a singleton. But it does deal with custom destruction methods that are passed in when a dynamic singleton is set. At that time, it registers the destruction with the SingletonStack . There is a helper template that makes it simple for users to pass in these destruction methods easily.

It is these details, and integration of the subtle variations of singletons, that prove to be a bit of work. If done by hand for each singleton, I would ask if an individually written singleton is the best choice for a robust system.

Other Benefits

Singletons in a unified framework can be accessed across component and dll boundaries. In fact, singletons could serve as the only interface between components. One component could get a singleton that is provided by another. Users would get this for free and do nothing special when retrieving a foreign singleton. Gets are resolved at linking time so there is no runtime performance penalty.

Developers also can have full access to all static singletons before main is executed. There could be a complex initialization routine during program static initialization. They need not worry whether a singleton is ready since the system ensures it will be, and by the way, destroyed properly. The system works equally well before or during execution of main. This is a difficult task to do outside such a system, and I had personally never seen anything do it correctly, much less on a large scale.

This framework also allows for singletons to be template template parameters. In modern generic programming, this provides a powerful mechanism for many advanced techniques.

Future Directions

The unified singleton framework can serve as the foundation for more complex systems. To date, factory systems, state machines, and resource managers have all been built onto using this architecture. Since no requirements are placed on any of their constituent, i.e. forced base classes or required implementation, it is easy to build on top of.

Conclusion

Singletons can provide a basis for other components in a modular system. They can come in two flavours: static and dynamic. Each has a trade-off of capabilities. The unified system maintains a live singleton stack. Singletons are destroyed in the reverse order to that in which they were created. Cyclic dependencies represent an invalid design and minimally can be detected by the singleton framework at run-time. Best of all, it provides a technique to solve the large-scale dependencies and life cycles of all singleton objects.

Full code of a unified singleton framework and usage examples can be found at: http://daudel.org/code/Singleton.zip

EDITORIAL COMMENT

This article prompted considerable debate amongst the members of the editorial team. Some feel it should not be published in this form because it encourages excessive use of the Singleton pattern, while others feel that it deserves publication because it describes a valid technique for implementing Singleton classes.

Singleton does two things: 1) it ensures that no more than one instance of a class can exist in a program and 2) it makes it easy to access the single object from anywhere in the program. The first is sometimes (although rarely) useful. The second is an invitation to excessive coupling which needs to be resisted.

This is an unusual situation for the editorial board, as most issues are fully resolved before publication, and I rarely (if ever) comment on articles directly in these pages. We are not here to censor though, just to guide authors through the process of creating technically correct, well written, and interesting articles. We would certainly welcome any comments from the readership about this article for publication in Overload 57.

John Merrells, Editor

References

[Gamma-et-al] Design Patterns by Gamma et al., 1995

[Alexandrescu] Modern C++ Design by Andrei Alexandrescu, 2001

[Meyers] Effective C++: 50 Specific Ways to Improve Your Programs and Design (2nd Edition) by Scott Meyers, 1997






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.