Odds and ends: the Singleton pattern
Hello, everybody.
This week, more content from Implementing QuantLib: QuantLib’s implementation of the much maligned Singleton pattern. And no, I didn’t say unjustly maligned.
Mandatory plug: there are still places available for my next Introduction to QuantLib Development course. If you want to hear me describe the architecture of the library and then put you through the paces with coding exercises, that’s the place to be. Tell it to whoever manages your training budget. More info in the banner above.
Subscribe to my Substack to receive my posts in your inbox, or follow me on Twitter or LinkedIn if you want to be notified of new posts, or subscribe via RSS if you’re the tech type: the buttons for all that are in the footer. Also, I’m available for training, both online and (when possible) on-site: visit my Training page for more information.
The Singleton pattern
The Gang of Four devoted the first part of their book [1] to creational patterns. While logically sound, this choice turned out to have an unfortunate side effect: all too often, overzealous programmers would start to read the book and duly proceed to sprinkle their code with abstract factories and singletons. Needless to say, this does less than intended for the clarity of the code.
You might suspect the same reason for the presence of a
Singleton
class template in QuantLib. (Quite maliciously, I
might add. Shame on you.) Fortunately, we can base our defense on
version-control logs; such class was added to the library later than,
say, Observer (a behavioral pattern) or Composite (a structural one).
Our default implementation is shown in the following listing.
It’s based on the Curiously Recurring Template Pattern, that I
described in a previous post; to be a
singleton, a class C
needs to inherit from Singleton<C>
, to
provide a private constructor taking on arguments, and to make
Singleton<C>
a friend so that it can use it. You can see an example
in the Settings
class.
As suggested by Scott Meyers [2], the map holding the instances (bear
with me) is defined as a static variable inside the instance
method.
This prevents the so-called static initialization order fiasco, in
which the variable is used before being defined, and in C++11 it has
the additional guarantee that the initialization is thread-safe (even
though that’s not the whole story, as we’ll see).
Now, you might have a few questions; e.g., why a map of instances if
this is supposed to be a singleton? Well, that’s because having a
single instance might be limiting; for instance—no pun
intended—you might want to perform simultaneous calculations on
different evaluation dates. Thus, we tried to mitigate the problem by
allowing one Singleton
instance per thread. This is enabled by
a compilation flag, and causes the instance
method to use the
#if
branch in which it gets an id from a sessionId
function and uses it to index into the map. If you enable per-thread
singletons, you must also provide the latter function; it will
probably be something like
in which you will identify the thread using the functions made
available by your operating system (or some threading library), turn
the identifier into a unique integer, and return it. In turn, this
will cause the instance
method to return a unique instance per each
thread. If you don’t enable the feature, instead, the id will always
ever be 0
and you’ll always get the same instance. In this case,
you probably don’t want to use threads at all—and in the other case,
you obviously do, but you have to be careful anyway: see the
discussion in my post on global settings.
You might also be asking yourself why I said that this is our
default implementation. Nice catch. There are others, which I
won’t show here and which are enabled by a number of compilation
flags. On the one hand, it turned out that the static-variable
implementation didn’t work when compiled as managed C++ code under
.NET (at least with older Visual Studio compilers), so in
that case we switch it with one in which the map is a static class
variable. On the other hand, if you want to use a global
Singleton
instance in a multi-threaded setting, you want to
make sure that the initialization of the Singleton
instance is
thread-safe (I’m talking about the instance itself, not the map
containing it; it’s where the new T
is executed). This
requires locks, mutexes and stuff, and we don’t want to go near any of
that in the default single-threaded setting; therefore, that code is
behind yet another compilation flag. You can look at it in the
library, if you’re interested.
Your last question might be whether we should have a Singleton
class
at all—and it’s a tough one. Again, I refer you to the previous
discussion of global settings. At this time, much like democracy
according to Winston Churchill, it seems to be the worst solution
except for all the others.
Bibliography
[1] E. Gamma, R. Helm, R. Johnson and J. Vlissides, Design Patterns: Element of Reusable Object-Oriented Software. Addison-Wesley, 1995.
[2] S. Meyers, Effective C++, 3rd edition. Addison-Wesley, 2005.