【问题标题】:Singleton with multiple inheritance具有多重继承的单例
【发布时间】:2021-11-02 11:20:07
【问题描述】:

我一直在尝试找到一个单例实现,其中:

  • 我可以有一个抽象类Application
  • 并且可以有多个从Application 派生的类,例如SampleApplicationOtherSampleApplication
  • 但我只想要Application 的一个实例,无论实际类型是什么,所以我一直在尝试将其设为单例。

这是我当前的单例实现:

template<class T>
class Singleton
{
public:
    static T& GetInstance()
    {
      static T instance;
      return instance;
    }

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

protected:
    Singleton() = default;
    virtual ~Singleton() = default;
};

然后,对于派生类,我尝试了很多方法:

  1. class Application : public Singleton&lt;Application&gt;class SampleApplication : public Application
    这不起作用,因为我们正在尝试创建 Application 实例而不是 SampleApplication
  2. class Application : public Singleton&lt;Application&gt;class SampleApplication : public Application, public Singleton&lt;SampleApplication&gt;
    这不会编译,因为 GetInstance() 不明确。我尝试使用 virtual 继承解决此问题,但无法编译。
  3. class Applicationclass SampleApplication : public Application, public Singleton&lt;SampleApplication&gt;
    不符合我上面列出的“要求”,因为我们可以拥有另一个派生类的另一个单例。

有谁知道实现这一点的方法吗?

【问题讨论】:

  • 一种可能的方法是使用多重继承 (2) 并在 SampleApplication 类中明确指定首选方法:public: using Singleton&lt;SampleApplication&gt;::GetInstance;
  • Application abstract 具有 GetInstance,或者 Singleton&lt;Application&gt;,并且 main 传递到派生的 Application 实例中。如果单例已经用实例初始化,它不允许第二次重新初始化(抛出异常?返回错误代码?静默忽略?)。
  • 如果你让它工作,你打算如何使用它?我不清楚您打算让动态调度进入的位置,因为您在单例中存储了一个普通的T
  • 只能有一个Application 对象,并且该对象是从Application 派生的类的子对象。因此,只有一个派生自Application 的类在程序运行时被实例化,对吧?你在编译时知道这是哪个派生类吗?
  • @dewaffled 这样做并添加另一个 OtherSampleApplication 类,您可以拥有如下内容:SampleApplication&amp; application = SampleApplication::GetInstance(); OtherSampleApplication&amp; otherApplication = OtherSampleApplication::GetInstance();

标签: c++ inheritance singleton


【解决方案1】:

既然您提到您将接受运行时错误:

template<class T>
class Singleton : private ReallySingleton {

单例的其余部分保持原样。私有基类将如下所示:


class ReallySingleton {

   static int counter=0;

protected:
   ReallySingleton()
   {
      if (++counter > 1)
        throw std::runtime_error{"Too many singletons"};
   }
};

ReallySingleton::counter 需要在方便的地方定义)

【讨论】:

    【解决方案2】:

    这个解决方案展示了只允许一个基于基类的实例是如何工作的:

    #include <iostream>
    #include <type_traits>
    
    //---------------------------------------------------------------------------------------------
    // type for checking if something is a singleton later
    struct SingletonBase 
    {
    };
    
    //---------------------------------------------------------------------------------------------
    // specialized singleton to be used in baseclass (Application)
    // keeps track of whether an instance of the baseclass has already been made
    template<typename T>
    struct Singleton : 
        public SingletonBase
    {
        Singleton() 
        {
            has_instance = true;
        }
    
        static bool has_instance;
    };
    
    // has_instance initilization
    template<typename T>
    bool Singleton<T>::has_instance = false;
    
    //---------------------------------------------------------------------------------------------
    // Baseclass for applications.
    
    class Application :
        public Singleton<Application>
    {
    protected:
        Application() = default; // do not allow client code to make instance of base class
    };
    
    class SomeOtherKindOfApplication :
        public Singleton<SomeOtherKindOfApplication>
    {
    protected:
        SomeOtherKindOfApplication() = default;
    };
    
    //---------------------------------------------------------------------------------------------
    // application types derived from Application and SomeOtherApplication 
    
    class Application1 :
        public Application
    {
    public:
        void Hello() 
        {
            std::cout << "Hello, " << std::endl;
        }
    };
    
    class Application2 :
        public Application
    {
    public:
        void World()
        {
            std::cout << "World !" << std::endl;
        }
    };
    
    class SomeOtherKindOfApplication1 :
        public SomeOtherKindOfApplication
    {
    public:
        void Bye()
        {
            std::cout << "Bye!" << std::endl;
        }
    };
    
    class NotASingleton
    {
    };
    
    //---------------------------------------------------------------------------------------------
    // Singleton instance getter
    
    template<typename T>
    static T& GetInstance()
    {
        static_assert(std::is_base_of_v<SingletonBase, T>, "Can only create instances of classes derived from singleton");
        if (T::has_instance)
        {
            throw std::runtime_error("An application instance has already been made");
        }
    
        static T instance;
        return instance;
    }
    
    //---------------------------------------------------------------------------------------------
    
    int main()
    {
        auto instance = GetInstance<Application1>();
        instance.Hello();
    
        try
        {
            // getting an instance of another application derived
            // from Application will fail (runtime)
            auto instance2 = GetInstance<Application2>();
            instance2.World();
        }
        catch (const std::exception& e)
        {
            std::cout << e.what() << std::endl;
        }
    
        // but getting application with another baseclass should be fine
        auto instance3 = GetInstance<SomeOtherKindOfApplication1>();
        instance3.Bye();
    
        // next line will not even compile, NotASingleton isn't a singleton
        // auto no_instance = GetInstance<NotASingleton>();
    }
    

    【讨论】:

      【解决方案3】:

      如果有多个从Application 派生的类,这种方法会产生编译时错误。通常。它不是万无一失的,它确实有缺点。我认为值得考虑。
      鉴于类名“Application”,我猜这可能是库的一部分。如果库不是仅标头,还有一个额外的考虑,我将在最后解决这个问题。

      这种方法的总体情况是简单的继承,对于定义从Application 派生的类的人来说,还有一个额外的步骤。

      class Application       : public Singleton<Application> { /* ... */ };
      class SampleApplication : public Application            { /* ... */ };
      REGISTER_APPLICATION(SampleApplication)     // Must not be in a header
      

      最后一行会在后面解释。

      首先,让我们隐藏Singleton&lt;Application&gt;::GetInstance(),因为Application 是抽象的,所以它不会编译。 (我假设每个人都会键入更短的 Application::GetInstance() 来使用这个函数;如果没有,这个方法就废了。)将函数声明添加到抽象类,但还没有添加定义。

      class Application : public Singleton<Application> {
        public:
          static Application & GetInstance();
      
          // ...
      };
      

      定义将被添加到别处,不在头文件中。从头文件中取出定义是在Singleton模板中隐藏函数的原因。 (虽然extern 关键字允许将一些模板定义从头文件中移出,但我的理解是,当有一个内联定义时,它不会完全抑制,就像在这种情况下一样。)GetInstance() 的定义将在哪里存在?这就是额外步骤的用武之地。这也是我可能会受到抨击的地方,因为除非需要它们,否则宏是邪恶的。这是需要的。把这个函数的定义放在一个宏里。

      #define REGISTER_APPLICATION(name)       \
      Application& Application::GetInstance()  \
      {                                        \
          static name instance;                \
          return instance;                     \
      }
      

      现在记录从Application 派生时,此宏必须在源文件中使用,而不是在头文件中。宏的参数是派生类的名称;如果派生类是SampleApplication,那么要添加到源文件的行是REGISTER_APPLICATION(SampleApplication)


      那么当有人搞砸了会发生什么?

      如果不使用宏,链接器将抱怨对Application::GetInstance() 的未定义引用。不幸的是,这并不像我想的那样清楚。可能会在您的框架的常见问题解答中添加一个条目来涵盖这一点。

      如果宏被使用两次,链接器将抱怨Application::GetInstance() 的多个定义。这就是为什么不能内联给出定义的原因。我们希望应用一个定义规则。这不是万无一失的(可能有两个从Application 派生的类,只有一个记得宏),但它可能就足够了。


      对于Application 的部分定义在标头之外的库,会出现Application::GetInstance() 不能成为库的一部分的问题,从而导致编译库时出现未定义的引用。一种解决方案是使Application 成为另一个应该在库内部的类的包装器。例如:

      class Application : public InternalApplication, public Singleton<Application> {
          static Application & GetInstance();
          // Everything else is inherited from InternalApplication.
      };
      

      现在Application 可以是仅标头,而InternalApplication 可以在静态(或动态)库中包含组件。

      【讨论】:

        猜你喜欢
        • 2011-09-12
        • 1970-01-01
        • 2014-07-01
        • 1970-01-01
        • 2022-01-20
        • 2021-11-28
        • 1970-01-01
        • 2015-12-13
        • 1970-01-01
        相关资源
        最近更新 更多