Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Singletons, curiously recurring template pattern and forwarding constructor parameters

Ok, I know Singletons should be avoided, however there are few instances where one really needs them. So my solution implements them using the CRTP (curiously recurring pattern), as follows:

#include <iostream>
#include <utility>

using namespace std;

template<typename T> // Singleton policy class
class Singleton
{
protected:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
public:
    template<typename... Args>
    static T& getInstance(Args... args) // Singleton
    {
        // Guaranteed to be destroyed.
        // Instantiated on first use.
        // Thread safe in C++11
        static T instance{std::forward<Args>(args)...};
        return instance;
    }
};

class Foo: public Singleton<Foo>
{
    friend class Singleton<Foo>;
    Foo()
    {
        cout << "Constructing instance " << this <<" of Foo" << endl;
    }
    Foo(int x)
    {
        cout << "Constructing instance " << this <<" of Foo with argument x = "\
             << x << endl;
    }
    ~Foo()
    {
        cout << "Destructing instance " << this << " of Foo" << endl;   
    }
public:
    // public 
};

int main()
{
    Foo& rfoo = Foo::getInstance(); // default constructible

    // this should just return the instance
    // instead, it constructs another instance
    // because invokes an overloaded version of get_instance()  
    Foo& rfoo1 = Foo::getInstance(1); 

    // this is OK
    // calls the SAME overloaded version again, instance is static
    // so we get the same instance
    Foo& rfoo2 = Foo::getInstance(2); 
}

As you see, I allow the possibility of creating singletons out of classes with overloaded/non-defaulted constructors. But this comes and bites me back, because, by using a variadic-templated get_instance() function and passing the instance parameters via std::forward, the compiler generates an overload for each call with different types, and therefore returns a new static instance for each overload. What I would like is to return by reference a single instance common somehow to all possible overloads, but cannot figure out how to do it. Any ideas how to do it? Thanks!

PS: I can implement a solution with pointers instead of references, but I prefer the reference solution as it is thread safe and more elegant in my opinion.

like image 706
vsoftco Avatar asked Jul 25 '14 21:07

vsoftco


2 Answers

Sorry I finally had the time. You could try this:

#include <iostream>
#include <utility>
#include <functional>

using namespace std;

template<typename T> // Singleton policy class
class Singleton
{
protected:
    Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    virtual ~Singleton() = default;
public:
    template<typename... Args>
    static T& getInstance(Args... args) // Singleton
    {
        cout << "getInstance called" << std::endl;

        //we pack our arguments in a T&() function...
        //the bind is there to avoid some gcc bug
        static auto onceFunction =  std::bind( createInstanceInternal<Args...>, args... ); 
        //and we apply it once...
        return apply( onceFunction );
    }

private:

    //This method has one instance per T 
    //so the static reference should be initialized only once 
    //so the function passed in is called only the first time
    static T& apply( const std::function<T&()>& function  )
    {
        static T& instanceRef = function();
        return instanceRef;
    }

    //Internal creation function. We have to make sure it is called only once...
    template<typename... Args>
    static T& createInstanceInternal(Args... args)
    {
        static T instance{ std::forward<Args>(args)... };
        return instance;
    }
};

IdeOne link:

http://ideone.com/Wh9cX9

Edit (thread safety && design concerns):

As far as I know this should be thread safe in C++11. Both instance and instanceRef are static local variables and should be thread safe initialized only once. Depending on your compiler, it might be however that the C++11 thread safe initialization is not implemented as speciffied by the standard. You could explicitly sinchronize within apply as a temporary workaround if this is the case. In my opinion, it is still disturbing that client code could call getInstance without parameters and with parameters. If the client code calls with parameters than most probably it has the expectation/need the singleton be initialized with the given parameters. Offering more than one initialization possibility will lead to unexpected/unnatural behavior for client code. That cannot be good. Should be fine if Foo has only one parameterizad ctor. However this would mean you allways have to pass some arguments to get the instance... In the end, having the singleton getInstance parameterized is going to cause more problems than it solves. That being said I simply could not ressist the intellectual challenge... :-).

like image 70
ds27680 Avatar answered Nov 01 '22 00:11

ds27680


The following code works for me in a single threaded program. Not sure how it has to be adapted for a multi-threaded program.

template<typename T> // Singleton policy class
class Singleton
{
   protected:
      Singleton() = default;
      ~Singleton() = default;
      Singleton(const Singleton&) = delete;
      Singleton& operator=(const Singleton&) = delete;

   public:

      struct PointerWrapper
      {
         T* instancePtr;
         PointerWrapper() : instancePtr(NULL) {}
         ~PointerWrapper() { delete instancePtr; }
      };

      template<typename... Args>
         static T& getInstance(Args... args) // Singleton
         {
            if ( NULL == wrapper.instancePtr )
            {
               wrapper.instancePtr = new T{std::forward<Args>(args)...};
            }
            return *(wrapper.instancePtr);
         }

      static PointerWrapper wrapper;
};

template <typename T> typename Singleton<T>::PointerWrapper Singleton<T>::wrapper;
like image 42
R Sahu Avatar answered Nov 01 '22 00:11

R Sahu