First things first: if you’re still reading this blog, thanks! I haven’t posted anything since last December; it has been a rough first half of the year: COVID-19, working from home, isolation, you know the deal, but I have not given up on blogging, just finding the time for it has been nearly impossible. I have a long list of topics I eventually want to cover but I can’t make any promises until this mad situation normalizes…
Alright then! A friend from work asked me about the Singleton Design Pattern and how to best implement it in C++. I have done it in the past but was not happy with that implementation; it insisted that the singleton class have a default constructor for example, so I started coding something that would allow non-default initialization. The first thing I came up with was a singleton template base class using the Curiously Recurring Template Pattern. It worked but it required you to declare its constructor private and declare the singleton base class a friend:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class S final : public singleton<S> { public: ~S() { cout << "~S()" << endl; } void foo() { cout << "S::foo() x = " << _x << endl; } void bar() const { cout << "S::bar() x = " << _x << endl; } private: // Constructor must be private to prevent creation of instances... // ...except by singleton<T> base class, which is our friend... friend class singleton<S>; S(int x) : _x(x) { cout << "S(" << _x << ")" << endl; } int _x = 0; }; |
This approach allowed me to separate the singleton creation S::Create(17); from usage S::Instance()->foo(); but I was still bothered by the need for private constructor(s) and friendship, so I kept experimenting… I wanted a simpler solution, one that by the very nature of inheriting the singleton base class would automatically render the class non-instantiable by anyone other than the parent template:
1 2 3 4 5 6 7 8 9 10 11 |
class AS : public abstract_singleton<AS> { public: AS(int x) : _x(x) { cout << "AS(" << _x << ")" << endl; } ~AS() { cout << "~AS()" << endl; } void foo() { cout << "AS::foo() x = " << _x << endl; } void bar() const { cout << "AS::bar() x = " << _x << endl; } private: int _x = 0; }; |
The name of the singleton base class probably gave away the approach, but let me explain anyway: the abstract_singleton<AS> base injects a pure virtual method, preventing one from creating instances of class AS. The parent class later erases the abstraction by implementing the private pure virtual method before creating an instance of AS (it actually creates an instance of a private type derived from AS, the compiler takes care of the rest; this works because in C++ access and visibility of a member are two distinct concepts):
1 2 3 4 5 |
struct Q : T { using T::T; void __abstract_singleton__() override {} }; |
One can of course easily defeat the mechanism by which instance creation is restricted by implementing the void abstract_singleton() override {} in the class meant to be a singleton. I don’t think there is much that can be done about that, but if it’s not done on purpose the compiler will detect attempts of creating instances and will fail with cannot instantiate an abstract class error.
Here’s the example program (singleton.cpp):
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 |
#include <iostream> #include "singleton.hpp" using namespace std; SINGLETON_CLASS(A) {}; SINGLETON_STRUCT(B) {}; struct C final : public singleton<C> {}; ABSTRACT_SINGLETON_CLASS(AA) {}; ABSTRACT_SINGLETON_STRUCT(AB) {}; struct AC : public abstract_singleton<AC> {}; class S SINGLETON(S) { public: ~S() { cout << "~S()" << endl; } void foo() { cout << "S::foo() x = " << _x << endl; } void bar() const { cout << "S::bar() x = " << _x << endl; } private: // Constructor must be private to prevent creation of instances... // ...except by singleton<T> base class, which is our friend... SINGLETON_FRIEND(S); S(int x) : _x(x) { cout << "S(" << _x << ")" << endl; } int _x = 0; }; class AS ABSTRACT_SINGLETON(AS) { public: // No friendship needed if constructors are public... // ...but you still can't create instances of AS... // ...except by abstract_singleton<T> base class... // ...which internally erases the abstraction... //ABSTRACT_SINGLETON_FRIEND(AS); AS(int x) : _x(x) { cout << "AS(" << _x << ")" << endl; } ~AS() { cout << "~AS()" << endl; } void foo() { cout << "AS::foo() x = " << _x << endl; } void bar() const { cout << "AS::bar() x = " << _x << endl; } private: int _x = 0; }; int main() { S::Create(17); //S s(17); // Compile-time error, can't create instances... try { S::Create(17); } catch(exception& e) { cout << e.what() << endl; } //*S::Instance() = *S::Instance(); // Compile-time error, can't copy/move singletons... S::Instance()->foo(); S::Instance()->bar(); AS::Create(20); //AS s(20); // Compile-time error, can't create instances... try { AS::Create(20); } catch(exception& e) { cout << e.what() << endl; } //*AS::Instance() = *AS::Instance(); // Compile-time error, can't copy/move singletons... AS::Instance()->foo(); AS::Instance()->bar(); cout << "Done!" << endl; } |
And the complete listing (singleton.hpp):
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 |
#include <mutex> #include <memory> #include <utility> #include <stdexcept> template<typename T> class singleton { public: template<typename... Args> static void Create(Args&&... args) { static std::mutex s_lock; std::scoped_lock lock(s_lock); if(!s_instance) s_instance.reset(new T(std::forward<Args>(args)...)); else throw std::logic_error("This singleton has already been created!"); } static T* Instance() noexcept { return s_instance.get(); } protected: singleton() = default; singleton(const singleton&) = delete; singleton(singleton&&) = delete; singleton& operator = (const singleton&) = delete; singleton& operator = (singleton&&) = delete; ~singleton() = default; private: using storage_t = std::unique_ptr<T>; inline static storage_t s_instance = nullptr; }; #define SINGLETON(T) final : public singleton<T> #define SINGLETON_CLASS(C) class C SINGLETON(C) #define SINGLETON_STRUCT(S) struct S SINGLETON(S) #define SINGLETON_FRIEND(T) friend class singleton<T> template<typename T> class abstract_singleton { public: template<typename... Args> static void Create(Args&&... args) { static std::mutex s_lock; std::scoped_lock lock(s_lock); struct Q : T { using T::T; void __abstract_singleton__() override {} }; if(!s_instance) s_instance.reset(new Q(std::forward<Args>(args)...)); else throw std::logic_error("This abstract singleton has already been created!"); } static T* Instance() noexcept { return s_instance.get(); } protected: abstract_singleton() = default; abstract_singleton(const abstract_singleton&) = delete; abstract_singleton(abstract_singleton&&) = delete; abstract_singleton& operator = (const abstract_singleton&) = delete; abstract_singleton& operator = (abstract_singleton&&) = delete; virtual ~abstract_singleton() = default; private: using storage_t = std::unique_ptr<T>; inline static storage_t s_instance = nullptr; virtual void __abstract_singleton__() = 0; }; #define ABSTRACT_SINGLETON(T) : public abstract_singleton<T> #define ABSTRACT_SINGLETON_CLASS(C) class C ABSTRACT_SINGLETON(C) #define ABSTRACT_SINGLETON_STRUCT(S) struct S ABSTRACT_SINGLETON(S) #define ABSTRACT_SINGLETON_FRIEND(T) friend class abstract_singleton<T> |
Good to see you blogging again Martin!