通用Singleton包装类的设计

时间:2016-09-10 05:41:56

标签: c++ templates singleton

我们正在我们的项目中弃用ACE库,其中包含大约120个二进制文件,在许多二进制文件中我们使用了ACE_Singleton。在弃用之后我们不会有这个类,所以我们考虑编写自己的通用单例在我们共享的库中,这是所有这些二进制文件中共有的,我们想要实现的目标之一是,如果有人从这个类继承(使用CRTP)说Logger,甚至当Logger构造函数是public时,我们也不能创建两个logger对象。 为了说明我们假设我的单例类名是GenericSingleton而我的客户端类是logger,那么下面的代码应该抛出错误:

class Logger:public GenericSingleton<Logger>
{
   public:
      Logger()
      {
      }
};
int main()
{
   Logger obj;// first instance no issue
   Logger obj1; // second instance is problem as it is inherited from singleton 
}

那么有人可以建议我如何设计GenericSingleton以便在创建第二个对象时我应该得到编译时错误吗?

简而言之,如果我的派生类没有私有构造函数,析构函数复制构造函数等,那么可以在编译时使用static_assert 检查它吗?

3 个答案:

答案 0 :(得分:1)

构造函数无法在编译时知道 将调用它的次数或次数;构造函数只是函数,函数对它们的上下文一无所知。考虑在编译类时将评估任何static_assert,但这可以(并且几乎肯定会!)在实际实例化该类的代码中的完全不同的翻译单元中发生。

在任何情况下,这似乎都不太可能有用,因为你必须有一些方法来访问整个代码库中的单例。

此外,还不清楚为什么你想让你的单身人士拥有公共建设者。如果你想在编译时为一个完全任意的类强制执行单例行为只是添加一个继承声明,那你就不走运了。任意类可以,任意构造。

由于您正在从ACE单例转换,我建议您使用类似的API;请注意,ACE单例文档建议将您的单例构造函数设为私有。

但是,如果您只是希望某种方式强制您的客户端编写一个不能(轻松)被不正确地调用的构造函数,您可以这样做:

template <typename T>
class SingletonBase {
  protected: class P { friend class SingletonBase<T>; P() = default; };
  public:
     SingletonBase(P) {}
     static T& Instance() {
         static T instance { P {} };
         return instance;
      }
};

(您还需要delete基类的副本和移动构造函数。Here是一个工作示例用例。请注意declaring P's constructor =default does not prevent the singleton class from default-initializing instances of P。)

现在,因为基类构造函数采用类型为P的参数,所以单例类实现必须将P传递给其父类构造函数。但由于P的构造函数是私有的,单例类除了通过复制或移动构造之外不能构造P的实例,因此它的构造函数必须采用P的实例。但是由于P本身受到保护,只有单例class和父类实际上可以使用它,因此有效地唯一可能调用子构造函数必须使用Instance方法。

请注意,您不需要显式声明和定义singleton-class的构造函数,因为需要使用SingletonBase<Singleton>::P,所以构造函数很难看。您只需使用using声明公开构造函数:

using BASE = SingletonBase<Myclass>;
using BASE::SingletonBase;

答案 1 :(得分:0)

您需要确保无法在创建Logger的唯一实例的函数之外创建Logger的实例。

这是一个简单的实现。

template <typename T> class GenericSingleton {
   public:
      static T& instance() {
         static T instance_;
         return instance_;
      }
};

class Logger: public GenericSingleton<Logger>
{
   // Make sure that the base class can create the sole instance of
   // this class.
   friend GenericSingleton<Logger>;

   private:

      // Makes sure that you cannot create objects of the class
      // outside GenericSingleton<Logger>
      ~Logger() {}

   public:
      void foo() {}
};

int main()
{
   // OK. Call foo() on the only instance of the class.
   Logger::instance().foo();

   // Not OK.
   Logger obj1;
}

答案 2 :(得分:0)

我的建议是分开关注点。存在服务的概念(例如记录器)并且服务可能是也可能不是单身。但这是一个实施细节,因此是一个单独的问题。服务的消费者应该不知道它。

现在,在项目生命周期的后期,当你意识到单身人士是一个糟糕的想法时,你可以重构单身人士,而不必重构任何依赖它的代码。

e.g:

template<class Impl>
struct implements_singleton
{
    using impl_type = Impl;
    static impl_type& get_impl()
    {
        static impl_type _{};
        return _;
    }
};

struct logger_impl
{
    void log_line(std::string const& s)
    {
        std::clog << s << std::endl;
    }
};


struct logger
: private implements_singleton<logger_impl>
{
    void log_line(std::string const& s) {
        get_impl().log_line(s);
    }
};

void do_something(logger& l)
{
    l.log_line("c");
}

int main()
{
    logger a;
    logger b;

    a.log_line("a");
    b.log_line("b");   // behind the scenes this is the same logger
                       // but the user need not care

    do_something(a);    
}