The One: A Singleton Discussion

Published February 19, 2009 by Robin Tan, posted by GameDev.net
Do you see issues with this article? Let us know.
Advertisement

Abstract

The singleton is a recurring pattern used by programmers. The singleton pattern is easy to understand but has subtle implementation problems in C++. This article discusses those problems. The reader is assumed to have a basic understanding of the singleton pattern. There are many articles on the subject available on the net. Please read at least one as I assume some knowledge of singletons.

This article was originally published to GameDev.net back in 2002. It was revised by the original author in 2008 and published in the book Beginning Game Programming: A GameDev.net Collection, which is one of 4 books collecting both popular GameDev.net articles and new original content in print format.

Why singletons?

For starters, let's examine why we need singletons. Take a look at this program.

// main.cpp 
#include <iostream>
using namespace std; 

extern int x; 
class C 
{ 
	public: C() 
	{ 
		// may not print 3 
		cout << x << "\n"; 
	} 
}; 

C c; 

int main() 
{ 
	return 0; 
} 

// another.cpp 
int F() { return 3; } 
int x = F(); 

Whether the above program prints 3 or 0 becomes dependent on the order in which you link main.cpp and another.cpp. Switch them around and you get different results. The initialization of static objects is well defined with each translation unit (.cpp file), but not across multiple files. This is why we need singletons.

Meyers' Singleton

This solution was first proposed by Scott Meyers (author of Effective C++ books, good read!). It often goes in the form below.

class MyClass {
    MyClass() {} 
    ~MyClass() {} // undefined 
    MyClass(const MyClass &); 
    MyClass& operator=(MyClass);

public: 
    static MyClass& Get_Instance() 
    { 
        static MyClass instance; 
        return instance; 
    } 
};

The ctor (constructor), dtor (destructor), copy ctor and assignment operator are defined privately to avoid unauthorized creation. This simple solution works by having a local static variable inside a function. The variable is initialized the first time Get_Instance() is called and deallocated when main exits. It is an elegant solution that fulfils what many programmers need from a singleton. But Meyers' singleton is not without problems. Some immediate problems are

  1. By storing an auto object (one that is allocated on the stack), we cannot store derived classes.
  2. The order of destruction is undefined.

No problem, we store a local pointer instead of local instance. This would allow Get_Instance() to return a reference to its parent interface (perhaps an abstract interface).

// class MyChild : public Parent 
static Parent& MyChild::Get_Instance() { static Parent *p = new MyChild(); return *p; } 

Problem 1 is solved, except that the singleton now leaks because the destructor is never called. The obvious solution here then would be to store a local static smart pointer (like std::auto_ptr) instead of the raw pointer. Now that the first problem is taken care of, let's look at the second. Meyer's singleton is initialized when Get_Instance() is first called. We solved the initialization order problem but another problem remains - the order of destruction. A static variable inside a function is registered by at_exit(), and is deallocated when main exits. We have no control over when it gets called or when the destructors of global objects call the singleton - it may be already deallocated!

To prove my point, try the sample breakmeyers.zip (in the attached file) that is designed to break Meyer's singleton. In the breakmeyers code, notice the order of calls produced. After main exits, the singleton is deallocated and yet another call to Get_Instance() is made afterwards. For this simple class it probably wouldn't crash, but if your class has data members, it means those members won't be available anymore. This occurs because we are relying on the compiler to automatically register the deallocation using at_exit(). If we store a pointer instance, we could avoid this issue, but then no one would be deallocating it. We either leak or risk crashing. It is clear now that Meyer's singleton is insufficient for singletons that need to be called within destructors of global objects. Granted, such singletons are rare, but what would you do if you faced one?

In case you are wondering about what situations you might need a singleton that has defined order of construction/destruction, think about a global logging system.

Nifty Counter

The nifty counter singleton has a defined order of construction and destruction. This solution to the above problem is described in the book C++ Faqs (not the free online version). We observe that local variables used in functions have an undefined destruction order. Only pointers can choose their destruction time. In essence, this is how the nifty counter works.

Look at the niftycounter.zip sample in the attached file before I explain. A nifty counter class is created to hold a pointer to MyClass. The Get_Instance is removed from MyClass into the nifty counter instead. Note at the end of MyClass.h, there is a static instance of the nifty counter. Before you scream at the atrocity of declaring variables in headers, calm down and look at how it works. Every file that includes MyClass.h will have a local instance of the nifty counter.

The Nifty counter class stores a reference count of how many instances it was created. Within each translation unit, the order of initialization/destruction is well defined (I said this before). The order of construction/destruction is first in/last out (FILO). Since we have to include MyClass.h before using the singleton, we are unknowingly creating a local instance of the counter in every translation unit. Since this local counter is always destructed last in the translation unit, the order of destruction is well defined (when no one needs to use it anymore). Does this come for free? Well almost. Notice we have to create a local counter per translation unit. This means that starting/shutting down the program will take more time because we have to page those sections into memory. If you can live with that, this singleton should serve you well.

Sample Singleton

This is a bonus for those who have read the book Modern C++ Design by Andrei Alexandrescu. This is a singleton holder inspired by the book. In the book's implementation of singleton holder, it keeps track of the order of destruction using a list. I did not particularly like that way of implementation, so I created the singleton holder available as zerobsingleton.zip in the attached file instead. It follows the book's implementation closely. However, for the singleton lifetime, I provide a nifty counter and Meyer's singleton policy instead. The book explains in more detail how the template policy works. Take note the singleton requires a very compliant C++ compiler due to the C++ templates. While it should work on a recent C++ compiler, it does fail on some ancient, but still in-use compilers like Visual Studio 6.

More problems

We are not done yet. We have only solved the basic lifetime requirements for singletons. There are other problems.

Exception safety

Exceptions are normally caught in a try/catch block within main. If the ctor of the singleton throws, chances are you have not entered main yet. This imposes a serious design issue for the singleton. You normally have to design the singleton so that the ctor doesn't throw, as you can't catch the exception to report it properly. However, there is a way to throw exceptions from the ctor. Try/catch blocks can be used at function scope (known as function try blocks). You can design an exception trapping mechanism for global objects using function try blocks. There are archived discussions on USENET comp.lang.c++.moderated regarding this. I shall not go further because there is enough material to warrant another long discussion. There are many good articles online that explains function try blocks.

Thread safety

The future of the CPU is multi core and threading is a subject we all have to face. How should we do it in a singleton? The easiest solution would be to protect Get_Instance with a synchronization object (critical section). That would work but that would mean additional overhead when accessing the singleton. Synchronizing locks/unlocks are usually not cheap and if the singleton is called often, performance can be adversely affected. A better design would be to let the client decide when to lock when accessing the singleton. Immediately a problem pops up - there is a possibility that multiple threads are trying to create the singleton on their first call to Get_Instance.

An ingenious idea is the "Double Checked Locking Pattern". Alas, it doesn't solve the problem thoroughly as one would hope. In the absence of threading support from the current C++ standard library, the only guaranteed solution is to use some kind of platform specific synchronization object. In that case, the DCLP can be used with the sync object to protect the creation code of the singleton. The sample singleton is not thread safe at the moment. With the platform-independent threading available with Boost (www.boost.org), making the sample thread-safe should be do-able.

Abuse of singletons

Implementing a singleton is fraught with dangers. Singletons provide more safety than using global variables but most of the time they are used more often than they should be. Practical experience suggests than it is often possible to redesign so the singleton becomes a member variable or local static variable. Indeed, this should be a worthy goal to strive for. Do we really need the graphics renderer to be global? Can it be a member variable of an Application object instead? Does the factory for creating objects need to be a singleton? Wouldn't it be better if we localize all the creation code in a single module? Think about the functionality of the class. Is it necessary for it to be a singleton or a mere convenience? For convenience sake, take note of the problems you are generating by having a singleton. A redesign would almost always benefit in the long run.

Glocal

This last section I'm adding on after having years of experience with writing engines. Yes, globals are evil, and there are times where a singleton is really handy. We want to have our cake and eat it, right? Glocal is a term I vaguely remember hearing somewhere from school days, I don't think it's the same thing I'm saying but I thought it is a cool term which fits perfectly. Simply, you assign a global pointer to a local variable.

// Global.h, included by all other files 
extern MyClass * globalInstance; // main. Cpp 

int main() 
{ 
	MyClass localInstance; 
	globalInstance = &localInstance; 
	return 0; 
} 

This simple technique is exception safe and with a defined order of destruction yet. There are many systems which be exposed with glocals, and the order of creation and destruction is implicitly controlled by the variable definition. While most programmers cringe at exceptions due to increased size and speed, and I would agree somewhat, but I think in development, exceptions are very useful and can be turned off for shipping. Anyway exception safety is for another article, you can see my article "An Exceptional Quest". Of course, this means the "singleton" can only be used after it is created, and in most of the engine system, it can be. There are few cases where you need the nifty counter singleton (cout comes to mind) to have global destruction order.

Cancel Save
0 Likes 5 Comments

Comments

ChristopherRinehart

In your last example you say that you need to assign a global variable to a local, but in the code it shows the opposite.

July 17, 2013 09:51 PM
Matt-D
July 17, 2013 10:25 PM
LorenzoGatti

I'll spare you the commonplace objections to tutorials presenting singletons as something useful, as opposed to "I tried to solve my problem with singletons and now I have two problems", because you focus on the highly instructive technical aspects, i.e. having singletons not explode, rather than on singleton advocacy.

However, this article has a number of less ideological shortcomings:

  • All the code examples need to be included in the main text (cleaned up, shortened and made uniform) to allow better discussions and comparisons; referring vaguely to source files in nested zip archives is inconvenient and ineffective.
  • Specific subtleties like exception unsafety and thread unsafety need detailed explanations, or optimists aren't going to understand that the respective problems are bad and can happen to them.
  • Some of your views are very dated; unsurprising in a revised old article, but what was OK ten years ago has decayed to bad advice (e.g. worrying about template availability and visual C++ 6 compatibility) or inaccurate C++ (e.g. constructors and pointers).
  • You should assume contemporary and near-future compilers, and therefore perfect C++11 compliance; readers who need to work with brain-damaged compilers (old development kits for old consoles?) can be expected to know the workarounds they need to port standard C++ code to their platform quite well.
July 18, 2013 11:09 AM
pinebanana

std::auto_ptr? That's <a href="http://en.cppreference.com/w/cpp/memory">deprecated</a>, use a std::unique_ptr

July 18, 2013 03:37 PM
swiftcoder

I'm not sure we can escape the ideological concerns, either. It is stated that one needs singletons to solve interdependencies between modules with respect to static initialisation order, but it isn't clear to me what property of singletons are supposed to help with this.

In particular, you can solve this in a similar fashion with a Monostate, and this problem provides no motivation to limit access to a single entry point.

July 19, 2013 10:39 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement