Fun with C++: Singletons
I just realized that I’ve not posted anything technical in a long while. Here’s my effort to remedy the situation.
This is a modified version of the straightforward implementation of the “singleton” pattern (STFW yourself.)
Let me add this. I’m rather proud of this code, so I ask you please to give this code a read if you are even a little interested.
First the definition of the singleton (which I named Zingleton to be used in Zorvan) and the handle class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | //========================================================== #include <cassert> //========================================================== #define DEF_ZINGLETON(T) \ template<> T * Zingleton<T>::ms_theone = NULL; \ template<> int Zingleton<T>::ms_refcount = 0 //========================================================== template <typename T> class ZingletonHandle { public : inline ZingletonHandle (); inline ~ZingletonHandle (); inline T const * operator -> () const {return m_ptr;} inline T * operator -> () {return m_ptr;} inline T const & operator * () const {return *m_ptr;} inline T & operator * () {return *m_ptr;} protected : T * m_ptr; }; //========================================================== template <typename T> class Zingleton { friend class ZingletonHandle<T>; public : static ZingletonHandle<T> getZingleton () { return ZingletonHandle<T>(); } protected : Zingleton () { assert ((NULL == ms_theone && ms_refcount == 0) || (NULL != ms_theone && ms_refcount > 0)); assert ((NULL == ms_theone && ms_refcount == 0)); ms_theone = static_cast<T *>(this); ms_refcount = 1; } ~Zingleton () { --ms_refcount; if (ms_refcount <= 0) { ms_theone = NULL; ms_refcount = 0; } } private : static T * zingletonAcquire () { if (NULL == ms_theone) { assert (0 == ms_refcount); zingletonCreate (); } assert (ms_refcount >= 0); ++ms_refcount; return ms_theone; } static void zingletonRelease (T const * instance) { assert (instance == ms_theone); if (instance != ms_theone) return; assert (NULL != ms_theone); --ms_refcount; assert (ms_refcount >= 0); if (ms_refcount <= 0) zingletonDestroy (); } static void zingletonCreate () { assert (NULL == ms_theone); if (NULL == ms_theone) { ms_theone = new T (); ms_refcount = 0; } } static void zingletonDestroy () { if (NULL == ms_theone) return; assert (0 == ms_refcount); delete ms_theone; ms_refcount = 0; } protected : static T * ms_theone; static int ms_refcount; }; //========================================================== template<typename T> inline ZingletonHandle<T>::ZingletonHandle () : m_ptr (Zingleton<T>::zingletonAcquire()) { assert (NULL != m_ptr); } //---------------------------------------------------------- template<typename T> inline ZingletonHandle<T>::~ZingletonHandle () { assert (NULL != m_ptr); Zingleton<T>::zingletonRelease (m_ptr); } //========================================================== |
I think the code is clear enough, so I’m not gonna explain it any more! Let me just say that the handle class is there as a specific kind of smart pointer.
Here’s one way to use it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | //========================================================== #include <iostream> //========================================================== // Either include or paste the above code snippet here. //========================================================== class TestZ : Zingleton<TestZ> // "CRTP"? Say what? { public : TestZ () {cout << "+It's alive!" << endl;} ~TestZ () {cout << "+I'm dying here..." << endl;} void Print () {cout << "+Hello, world." << endl;} }; //---------------------------------------------------------- DEF_ZINGLETON (TestZ); //========================================================== void use_zingleton () { cout << "-Start of use_zingleton()..." << endl; ZingletonHandle<TestZ> h; h->Print (); cout << "-End of use_zingleton()." << endl; } //---------------------------------------------------------- int main () { cout << "-Start of main()..." << endl; { cout << "-Entered inner scope..." << endl; ZingletonHandle<TestZ> h; h->Print (); use_zingleton (); cout << "-End of inner scope." << endl; } cout << "-End of main()." << endl; return 0; } //========================================================== |
When (if) you run the above code, it should produce something like this:
1 2 3 4 5 6 7 8 9 10 | -Start of main()... -Entered inner scope... +It's alive! +Hello, world. -Start of use_zingleton()... +Hello, world. -End of use_zingleton(). -End of inner scope. +I'm dying here... -End of main(). |
As you can see, only one instance of the TestZ class is created and it is destroyed only after all the handles are destroyed. Basically, the handles are objects that guarantee that the singleton is alive and valid in their lifetime.
Homework: There is at least one very serious bug in the above code (that I’m unaware of.) What is it? It’s not the thread-safety issues because I’m aware of that and may fix and post again (that’s a great opportunity to talk about policy-based design in C++, so it demands another post or two.
DEF_ZINGLETON (ZingletonHandle);
damn it! not again! it removes the \\ part. i remember we got stuck at fixing the problem of letting others put code in the comments.
Anyhow I was wondering what would happen if I declared “ZingletonHandle [in angle brackets] TestZ” as zingelton.
Of course, i’ll need a “ZingletonHandle [in angle brackets] (ZingletonHandle [in angle brackets] TestZ)” to create an instance of “ZingletonHandle [in angle brackets] TestZ” and there we go.
Let’s have a convention that curly braces (“{}”) mean angle brackets (“<>”) in comments that contain code. I don’t think there’s any real chance of ambiguouity, is there?
And I’m a bit sleep-deprived right now (just crossed 24 hours, no coffee!) but I think your suggestion will trigger a compile error (perhaps an stack overflow in the compiler, or a recursion-depth-limit overflow at compile time.)
Any suggestions of how to make it impossible to do that? Is it even possible? Is it really necessary if it results in a compile error? After all, the compiler’s there to keep the user from shooting herself in her own foot!
Awesome, I can’t wait to Zingletonize a bunch of crappy Singletons in Zorvan
… how do you know there is a bug you are unaware of?
Hehe, stupid mistake of mine with the brackets. Anyways, why should it run into a compile error? There is no recursion going on. Defining ZH-TestZ to be zingelton will declare Z-ZH-TestZ which in turn gets friend with ZH-ZH-TestZ. So you can still instantiate a ZH-ZH-TestZ, get access to a singleton instance of ZH-TestZ, and using that get a singleton instance of TestZ. Now when the scope for the ZH-ZH-TestZ finishes, it will die, which will cause the instance of ZH-TestZ to die, which in turn will cause TestZ to die (I assume we already defined TestZ to be zingleton).
@ahf:
Because there is always a bug that the coder is unaware of!
@MatGill:
No recursion? Emmmm… I think that was the result of the sleep deprivation.
Of course, the most important bug in the above code is the fact that handles don’t behave correctly when copied. I should either disallow copying or fix the problem.
(Which makes more sense?)
Block copying and use an explicit function for replicating the handles. Maybe use a deep-copy copy constructor.