Одиночные, любопытно повторяющиеся шаблоны и параметры конструктора пересылки

Хорошо, я знаю, что синглетонов следует избегать, однако есть несколько случаев, когда они действительно нужны. Поэтому мое решение реализует их с использованием CRTP (странно повторяющийся шаблон) следующим образом:

#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); 
}

Как видите, я допускаю возможность создания синглетонов из классов с перегруженными / не дефолтными конструкторами. Но это приходит и кусает меня обратно, потому что, используя вариадические get_instance() функция и передача параметров экземпляра через std::forwardкомпилятор генерирует перегрузку для каждого вызова с разными типами и поэтому возвращает новый статический экземпляр для каждой перегрузки. Что я хотел бы, так это вернуть по ссылке один общий экземпляр, каким-то образом связанный со всеми возможными перегрузками, но не могу понять, как это сделать. Есть идеи как это сделать? Спасибо!

PS: я могу реализовать решение с указателями вместо ссылок, но я предпочитаю эталонное решение, поскольку оно, на мой взгляд, поточно-ориентированное и более элегантное.

2 ответа

Решение

Извините, я наконец-то нашел время. Вы можете попробовать это:

#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 ссылка:

http://ideone.com/Wh9cX9

Изменить (безопасность потока && вопросы дизайна):

Насколько я знаю, это должно быть поточно-ориентированным в C++11. И instance, и instanceRef являются статическими локальными переменными и должны быть инициализированы потокобезопасными только один раз. В зависимости от вашего компилятора может случиться так, что безопасная для потоков инициализация C++ 11 не реализована в соответствии со стандартом. В этом случае вы можете явно синхронизировать внутри apply в качестве временного обходного пути. На мой взгляд, все еще вызывает беспокойство тот факт, что клиентский код может вызывать getInstance без параметров и с параметрами. Если клиентский код вызывает с параметрами, то, скорее всего, он имеет ожидание / необходимость инициализации синглтона с заданными параметрами. Предложение более одной возможности инициализации приведет к неожиданному / неестественному поведению клиентского кода. Это не может быть хорошо. Должно быть хорошо, если Foo имеет только один параметризатор ctor. Однако это будет означать, что вы всегда должны передавать некоторые аргументы, чтобы получить экземпляр... В конце концов, если параметризовать синглтон getInstance, это вызовет больше проблем, чем решит. При этом я просто не мог противостоять интеллектуальному вызову...:-).

Следующий код работает для меня в однопоточной программе. Не уверен, как его нужно адаптировать для многопоточной программы.

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;
Другие вопросы по тегам