1 Context
Yesterday, I found the solution to a problem that I was unable to solve one year ago. The problem was simple: Creating a generic Singleton. If you don't know what a Singleton is, here is the definition given in the book "Design Pattern" from the Gamma et al.: "Ensure a class only has one instance, and provide a global point of access to it".
I wanted to have a generic singleton because I had to code several singletons in several projects and I hate making the same thing several times. I was unable to succeed in my tries because of the initialization problem. There was no known method to handle the initialization that works for each possible constructors.
A perfect way to change a class into a Singleton would be, in my opinion, something like just inheriting from a base class Singleton. That would be awesome! But I don't think anyone has ever succeed. The best discussion about a generic Singleton I have read is in the Andrei Alexandrescu's book (If you know a better stuff about it, please let me know!). It notably talks about the deletion problem, that is not approached in this article. I just focus on the construction that can be improved thanks to C++11.
The common version is to use the Curiously Recurring Template Pattern (CRTP). So let's start by introducing the CRTP. The concept is simple, we inherit from a template class that takes us as parameter. It allows to make static polymorphism or instance counter… Wikipedia may help for these examples.
I had to say that when I discovered this thing I was amazed. I am passionated by the concept of the template, by the language in the language. One year ago I tried to solve the Singleton with a CRTP (which is the solution used in the C++ Modern Design book), and my specifications was the original one, and I had to call a method "destroySingleton" at the end of my programs. So I have to find a generic constructor that works for all of my Singleton classes and the work is done.
2 First Try
At the beginning, when I had only two classes and two constructors, I though to play on the fact that the template functions not used are not instantiated, so they can be incorrect (no type checking, no name binding… It just must be valid syntactically). Here is an example:
#include <string> template <class T> class Singleton { public: static T* get_instance(int v) { if (!instance_) instance_ = new T(v); return instance_; } static T* get_instance(std::string s) { if (!instance_) instance_ = new T(s); return instance_; } // Sounds familiar... static void destroy_instance() { delete instance_; instance_ = nullptr; } private: static T* instance_; }; template <class T> T* Singleton<T>::instance_ = nullptr; // The classes that will be singletons: class Single: public Singleton<Single> { public: Single(std::string s): s_(s) {} private: std::string s_; }; class Map: public Singleton<Map> { public: Map(int scale): scale_(scale) {} private: int scale_; }; // How to create and destroy them: int main() { Single* s = Single::get_instance("Forever Alone"); Map* m = Map::get_instance(42); // Use these singletons. Map::destroy_instance(); Single::destroy_instance(); }
When we instantiate Singleton<Single>
there is no
constructor that takes an int (so the first get_instance
is incorrect), and the program still compiles
successfully. This is because the function is not called, so not
instantiated, so there is no reason to wine for g++.
This version respects one half of the deal to have a singleton, but it
is totally non-intrusive in the code of the classes transformed into
singleton. To respect the second half ("only one instance"), we have
to tweak a little. We have to make the constructors of the derived
classes private, and to grant the friendship to the mother class. As a
reminder, the friend
keyword allows to give access to
all of the protected/private methods to the friend class. This way, no
user can create directly an object of the derived class, and we respect the
specifications.
Now we respect the two part, but we are still unhappy with this. In
fact, we have only two classes, and we had to create two
get_instance
, because we have two different
constructors. Since the number of possible combination of arguments to
give to the constructor of an object is infinite, our solution isn't suitable. And that's
where I was stuck.
3 The Solution
And yesterday, I finally realized that the solution was just in one of
my previous post about the C++11 mechanisms that allows to create
emplace_back
. The solution was the perfect forwarding!
In fact, we just have to create a get_instance
that
forwards the arguments to the constructor. No repetition, fully
generic. That's what I wanted to reach! Here is the final version of
the Singleton class:
#include <iostream> template <class T> class Singleton { public: template <typename... Args> static T* get_instance(Args... args) { if (!instance_) { instance_ = new T(std::forward<Args>(args)...); } return instance_; } static void destroy_instance() { delete instance_; instance_ = nullptr; } private: static T* instance_; }; template <class T> T* Singleton<T>::instance_ = nullptr; class Map: public Singleton<Map> { friend class Singleton<Map>; private: Map(int size_x, int size_y): size_x_{size_x}, size_y_{size_y} {} public: int size_x_; int size_y_; }; int main() { Map* m = Map::get_instance(4, 5); std::cout << m->size_y_ << std::endl; // Outputs 5. Map::destroy_instance(); }
As said above, we are not interested in the destruction problem. It is
well-covered in the Alexandrescu's book, and I recommend you to read
it if you want to see more on the subject. We create a
destroy_instance
method in the aim to do not leak our
instance. The user must call it at the end of the program.
The real novelty of this, is the use of std::forward
when creating the object. So we can give to it any class, and it will
work. I take the example of a Map (for example for a game) which takes
two arguments, but I hope it is clear for everyone that it works with
any constructors.
Note that, I didn't write the copy and move constructors private, but they should be. I omit them only to gain some space in my post. For the same reason, I didn't write the class Single in the second example. But it is clear that it works.
Before concluding, I just want to show that the use of the
metaprogramming here doesn't lead to a hideous error message by our
compiler when not used correctly. Let's assume that we replace the call to
get_instance
with two arguments, by a call with no
arguments. What happens? Here is the answer of g++ (4.6.1):
singleton.cc: In static member function 'static T* \ Singleton<T>::get_instance(Args ...) [with Args = {}, T = Map]': singleton.cc:53:30: instantiated from here singleton.cc:16:9: erreur: no matching function for call to 'Map::Map()' singleton.cc:16:9: note: candidates are: singleton.cc:40:3: note: Map::Map(int, int) singleton.cc:40:3: note: candidate expects 2 arguments, 0 provided singleton.cc:35:7: note: constexpr Map::Map(const Map&) singleton.cc:35:7: note: candidate expects 1 argument, 0 provided singleton.cc:35:7: note: constexpr Map::Map(Map&&) singleton.cc:35:7: note: candidate expects 1 argument, 0 provided
The message is clear, and there is all the information needed to understand it, and to fix our mistake.
In this post I proposed the use of std::forward
for
building a generic singleton. I don't talk the deletion problem because
for my personal use, I accept to call the destroy method.
But this is just one example to show how the C++11 can help programmers to build new things easily. I hope my article motivates you to experiment new things! Feel free to share your opinion in comments :-)
PS: I just realize that my current solution isn't perfect. In fact, we can't get the instance without giving a valid argument list (valid = there is a constructor that takes this list). So a call to get_instance
without argument, which should return the instance leads to an error if there is no constructor that takes no argument. This is not really what we want. A fix would be to separate the initialization and the get_instance
. But that doesn't invalidate what I wanted to demonstrate. So it's okay :)
Awesome stuff! I have always wanted to solve this problem. Thanks for sharing.
ReplyDeleteThis is a really fancy way of using a really crappy design pattern. Singletons are simply a global variable - If you must have a single global object, why bother with all the singleton code and just make a global object?
ReplyDeleteThere is two parts in the definition of the Singleton, it also ensures that there is only one instance of this class.
DeleteIf I took this example it is mainly because it is very classical. I don't have any particular affect for global variable / Singleton.
is necessary a virtual destructor in the class singleton ?
ReplyDeleteNo it's not.
DeleteIMHO, since you call the destructor of the derived class, it's okay.
If it appears that I'm wrong, let me know :)
Yes! Also, inside the virtual destructor your could have a final check to see if the instance has been destroyed, and if not emit a warning and destroy it.
ReplyDeleteI don't understand what your advice. The virtual destructor will be called only if the pointer is deleted.
DeleteWhat happens in the destroy_instance method. At this point, we can't do nothing (instance_ is not a nullptr yet).
Maybe I miss your point?
what's the point of putting 'static' on a separate line?
ReplyDeleteNo point at all. I'm just strict with the 80 columns rules.
DeleteThe template, these arguments, 'static', the return type and the rest of the signature of the function don't fit on 80 columns. So I split the line. That's all.
It's just a personal habit.
two thoughts:
ReplyDelete1. beware that your singleton is not thread safe
2. I'm not sure this works if you include it in more than one translation unit
This implementation isn't written to be thread safe. The aim of this post is just to show how you can forward parameters to enhance the genericity of some piece of code. I found that this example was good for that. It's not hard to make it thread safe, that's just not the point here.
DeleteI don't know why it would be problematic?
This post was published on hacker new, so there were some comments on this site:
ReplyDeletehttp://news.ycombinator.com/item?id=4456835
Very elegant solution for Singleton :^)
ReplyDeleteI adopt for my developments. I will look later how to combine it with the thread taking the approach of Andrei Alexandrescu.
I will send you a version if I find the solution.
Nice article! What about using static std::shared_ptr ?
ReplyDelete