使用CRTP实施Singleton

时间:2018-08-22 20:31:23

标签: c++ singleton crtp

阅读this answer后,我尝试实现一些简单的CRTP用法。考虑到链接的答案种类已经做到了这一点,所以我认为我将尝试实现Singleton模式(是的,我知道–它仅用于实践和研究)。它不能编译的事实。

引用的代码如下:

template <class ActualClass> 
class Singleton
{
   public:
     static ActualClass& GetInstance()
     {
       if(p == nullptr)
         p = new ActualClass;
       return *p; 
     }

   protected:
     static ActualClass* p;
   private:
     Singleton(){}
     Singleton(Singleton const &);
     Singleton& operator = (Singleton const &); 
};
template <class T>
T* Singleton<T>::p = nullptr;

class A: public Singleton<A>
{
    //Rest of functionality for class A
};

然后我“现代化”到:

template <class T>
class Singleton {
public:
    Singleton()                              = delete;
    Singleton(const Singleton&)              = delete;
    Singleton(Singleton&&)                   = delete;
    Singleton& operator = (const Singleton&) = delete;
    Singleton& operator = (Singleton&&)      = delete;

    static T& get_instance() {
        if(!instance)
            instance = new T;
        return *instance;
    }

   protected:
     static inline T* instance = nullptr;
};

class A: public Singleton<A> {
    //Rest of functionality for class A
};

然后我尝试创建对该实例的引用:

auto& x = A::get_instance();

显然没有编译。

值得一提的是,我得到了非常相似的错误消息,特别是:

  

注意:'A :: A()'被隐式删除,因为默认定义格式不正确:class A : public Singleton<A>

显然,第二个代码段 无法编译,因为我们删除了默认构造函数,并尝试在new T方法中将其与get_instance一起使用。

令我惊讶的是,第一个代码片段也没有编译,并显示类似的错误消息。链接的答案是否有误?如何使用CRTP为Singletons实现通用基类/接口

3 个答案:

答案 0 :(得分:1)

第一个代码块的问题是Singleton(){}被标记为私有。这意味着A无法访问它,因此A不能成为默认构造。使构造函数protected可以解决此问题

template <class ActualClass> 
class Singleton
{
   public:
     static ActualClass& GetInstance()
     {
       if(p == nullptr)
         p = new ActualClass;
       return *p; 
     }

   protected:
     static ActualClass* p;
     Singleton(){}
   private:
     Singleton(Singleton const &);
     Singleton& operator = (Singleton const &); 
};
template <class T>
T* Singleton<T>::p = nullptr;

class A: public Singleton<A>
{
    //Rest of functionality for class A
};

int main()
{
    auto& x = Singleton<A>::GetInstance();
}

您的第二个代码块也有类似的问题,但是您没有将默认构造标记为private,而是将其标记为delete,所以它不是= default可构造的意思,A也不会是默认可构造。默认构造函数将其设为protected,如第一个示例将解决此问题

template <class T>
class Singleton {
public:

    Singleton(const Singleton&)              = delete;
    Singleton(Singleton&&)                   = delete;
    Singleton& operator = (const Singleton&) = delete;
    Singleton& operator = (Singleton&&)      = delete;

    static T& get_instance() {
        if(!instance)
            instance = new T;
        return *instance;
    }

protected:
    Singleton()                              = default;
    static inline T* instance = nullptr;
};

class A: public Singleton<A> {
    //Rest of functionality for class A
};

int main()
{
    auto& x = Singleton<A>::get_instance();
}

答案 1 :(得分:1)

(我认为)尽可能小的实施。

功能:

  • A不可复制,不可构造或移动。 (通过删除复制操作来隐式删除运算符)
  • 实现的构造是线程安全的。
  • 在程序结束时确保销毁实施。

template <class T>
struct Singleton 
{
    Singleton(const Singleton&)              = delete;
    Singleton& operator = (const Singleton&) = delete;

    static T& get_instance() {
        static T _{allow()};
        return _;
    }

private:
    struct allow {};

protected:
    Singleton(allow) {}
};

class A: public Singleton<A> {
    using Singleton<A>::Singleton;
    //Rest of functionality for class A
};

int main()
{
    auto& x = Singleton<A>::get_instance();
    auto& y = A::get_instance();

// compiler error
    auto z = A();
}

但是为什么不将“单一性”作为实现细节呢?用户为什么需要知道该对象是单例对象?

template <class T>
struct Singleton 
{
protected:
    static T& get_impl() {
        static T _;
        return _;
    }
};

// the real implementation of A
struct AImpl
{
    void foo();
};

// A is a value-type which just happens to be implemented in terms of a
// single instance
struct A: public Singleton<AImpl> 
{
    auto foo() { return get_impl().foo(); }
};

void bar(A a)
{
    a.foo();
}

int main()
{
    auto x = A();
    x.foo();

    auto y = A();
    y.foo();

    x = y;

    bar(x);
}

然后,如果您确定类型不应该是单例,则不需要更改其接口(因此也无需更改程序的其余部分):

示例-A是单例,而B不是。接口是相同的。

#include <memory>

template <class T>
struct Singleton 
{
protected:
    static T& get_impl() {
        static T _;
        return _;
    }
};

template<class T>
struct CopyableIndirect
{
    CopyableIndirect() = default;

    CopyableIndirect(CopyableIndirect const& r)
    : impl_(std::make_unique<T>(*r.impl_))
    {

    }

    CopyableIndirect(CopyableIndirect&& r)
    : impl_(std::move(r.impl_))
    {

    }

    CopyableIndirect& operator=(CopyableIndirect const& r)
    {
        auto temp = r;
        swap(temp);
        return *this;
    }

    CopyableIndirect& operator=(CopyableIndirect && r)
    {
        auto temp = std::move(r);
        swap(temp);
        return *this;
    }

    void swap(CopyableIndirect& r)
    {
        std::swap(impl_, r.impl_);
    }
protected:
    T& get_impl() {
        return *impl_;
    }

    T const& get_impl() const {
        return *impl_;
    }

   std::unique_ptr<T> impl_ = std::make_unique<T>();
};

struct AImpl
{
    void foo() const;
};

struct A: public Singleton<AImpl> 
{
    auto foo() const { return get_impl().foo(); }
};

struct B: public CopyableIndirect<AImpl> 
{
    auto foo() const { return get_impl().foo(); }
};

void bar(A const& a)
{
    a.foo();
}

void bar(B const& a)
{
    a.foo();
}

int main()
{
    auto x = A();
    x.foo();

    auto y = B();
    y.foo();

    bar(x);
    bar(y);
}

答案 2 :(得分:0)

以下是“现代化”代码段的mod:

template <class T>
class Singleton {
public:
    Singleton& operator = (const Singleton&) = delete;
    Singleton& operator = (Singleton&&)      = delete;

    static T& get_instance() {
        if(!instance)
            instance = new T_Instance;
        return *instance;
    }

protected:
    Singleton() {}

private:
    struct T_Instance : public T {
        T_Instance() : T() {}
    };

    static inline T* instance = nullptr;
};

class A : public Singleton<A> {
protected:
    A() {}
};

int main()
{
    auto& x = A::get_instance();
}

摘要摘要:

  • protected(单例中的默认构造函数)
  • private嵌套结构可访问派生类的受保护构造函数
  • 派生类中的
  • protected构造函数可防止实例化

此外,不需要通过向Singleton类添加默认ctor实现来隐式删除的delete构造函数。

并不像Richard Hodges的例子那么小,但是静态的instance成员可以轻松添加delete_instance()方法以用于自动化单元测试。