单身人士,好奇地反复出现模板模式和转发构造函数参数

时间:2014-07-25 21:28:28

标签: c++ templates c++11 singleton variadic-templates

好的,我知道应该避免使用Singletons,但是很少有人真正需要它们。所以我的解决方案使用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 个答案:

答案 0 :(得分:6)

抱歉,我终于有空了。你可以试试这个:

#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只有一个参数化,那么应该没问题。然而,这意味着你总是必须传递一些参数来获取实例......最后,将单例getInstance参数化将导致比它解决的更多问题。话虽如此,我根本无法解决智力挑战......: - )。

答案 1 :(得分:2)

以下代码适用于单线程程序。不确定如何适应多线程程序。

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;