C++ 单例类——继承的好习惯

标签 c++ oop design-patterns inheritance singleton

在现有的项目中,我要继承一个声明为Singleton的Controller类(MVC),以便定义我自己的处理方式。如何适本地派生这个 Singleton 类?

首先,我扩展了上下文和这种继承的需要。

我添加到现有软件中的应用程序想要使用一个 MVC 模块,该模块执行的任务与我愿意执行的任务几乎相同。它使用相同的方法进行签名和轻微修改。重写我自己的 MVC 模块肯定会重复代码。现有模块本质上是面向其应用到软件的另一部分,我不能简单地使用相同的模块。但是被写成模型- View - Controller 模式,其中 Controller 是单例。我已经派生了 View。

其次,我怀疑我可以经典地派生 Singleton 类。

从继承类调用构造函数只会为父类调用 getinstance() 并且无法从派生类 (?) 返回对象。

第三,这就是我所看到的一些处理方式。请评论/帮助我改进!

我将整个 Singleton 类复制到一个可以称为 AbstractController 的类中。我两次派生这个类。第一个 child 是单例,采用父类的整体处理。第二个 child 是我的应用程序部分的 Controller ,具有自己重新定义的处理方式。

谢谢!

最佳答案

事实是,单例和继承不能很好地结合在一起。

是的,是的,Singleton 爱好者和 GoF 崇拜者会为此倾倒我,说“好吧,如果你让你的构造函数受到保护......”和“你不必在类上有 getInstance 方法,你可以把它...”,但它们只是证明了我的观点。单例必须跳过许多障碍才能成为单例和基类。

但只是为了回答这个问题,假设我们有一个单例基类。它甚至可以在某种程度上通过继承来加强其单一性。 (当构造函数不再是私有(private)的时,它会做少数可以工作的事情之一:如果另一个 Base 已经存在,它会抛出异常。)假设我们还有一个类 Derived。继承自 Base .因为我们允许继承,所以我们也可以说 Base 可以有任意数量的其他子类。 ,可能会或可能不会从 Derived 继承.

但是有一个问题——你要么已经遇到了,要么很快就会遇到这个问题。如果我们拨打 Base::getInstance在没有构造对象的情况下,我们将得到一个空指针。我们想取回任何存在的单例对象(它可能是 Base ,和/或 Derived ,和/或 Other )。但是要做到这一点并且仍然遵守所有规则是很困难的,因为只有几种方法可以做到——而且所有方法都有一些缺点。

  • 我们可以创建一个 Base并将其归还。螺丝 DerivedOther .最终结果:Base::getInstance()总是准确返回 Base . child 类从来没有玩过。有点违背目的,IMO。
  • 我们可以放一个 getInstance我们自己的派生类,并让调用者说 Derived::getInstance()如果他们特别想要一个 Derived .这显着增加了耦合(因为调用者现在必须知道专门请求一个 Derived ,并最终将自己绑定(bind)到该实现)。
  • 我们可以做最后一个的变体——但不是获取实例,函数只是创建一个。 (在此期间,让我们将函数重命名为 initInstance ,因为我们并不特别关心它得到了什么——我们只是调用它以便它创建一个新的 Derived 并将其设置为 One真实实例。)

  • 所以(除非有任何未知的奇怪之处),它有点像这样......
    class Base {
        static Base * theOneTrueInstance;
    
      public:
        static Base & getInstance() {
            if (!theOneTrueInstance) initInstance();
            return *theOneTrueInstance;
        }
        static void initInstance() { new Base; }
    
      protected:
        Base() {
             if (theOneTrueInstance) throw std::logic_error("Instance already exists");
             theOneTrueInstance = this;
        }
    
        virtual ~Base() { } // so random strangers can't delete me
    };
    
    Base* Base::theOneTrueInstance = 0;
    
    
    class Derived : public Base {
      public:
        static void initInstance() {
            new Derived;  // Derived() calls Base(), which sets this as "the instance"
        }
    
      protected:
        Derived() { }   // so we can't be instantiated by outsiders
        ~Derived() { }  // so random strangers can't delete me
    };
    

    在你的初始化代码中,你说 Base::initInstance();Derived::initInstance(); ,取决于您希望单例是哪种类型。您必须从 Base::getInstance() 转换返回值为了使用任何 Derived - 特定的函数,当然,但无需强制转换,您可以使用 Base 定义的任何函数,包括被 Derived 覆盖的虚函数.

    请注意,这种做法也有其自身的许多缺点:
  • 它将强制单例的大部分负担放在了基类上。如果底座没有这个或类似的功能,而且你不能改变它,那你就有点搞砸了。
  • 然而,基类不能承担所有的责任——每个类都需要声明一个 protected 析构函数,或者有人可以在适本地(在)强制转换后删除一个实例,然后整个事情就变得很糟糕了。更糟糕的是,这不能由编译器强制执行。
  • 因为我们使用 protected 析构函数来防止一些随机的傻瓜删除我们的实例,除非编译器比我担心的更聪明,否则即使运行时在程序结束时也无法正确删除您的实例。再见,RAII...你好“检测到内存泄漏”警告。 (当然,内存最终会被任何体面的操作系统回收。但是如果析构函数没有运行,你就不能依赖它为你做清理。你需要在你之前调用某种清理函数退出,这不会给你任何与 RAII 可以给你相同的保证。)
  • 它暴露了一个 initInstance IMO 并不真正属于每个人都可以看到的 API 的方法。如果你愿意,你可以制作 initInstance私有(private)并让你的 init 函数是一个 friend ,但是您的类(class)正在对自身之外的代码做出假设,而耦合的事情又开始发挥作用。

  • 还要注意,上面的代码根本不是线程安全的。如果你需要那个,你就靠你自己了。

    说真的,不那么痛苦的方法是忘记尝试强制单例。确保只有一个实例的最简单的方法是只创建一个。如果需要多处使用,可以考虑依赖注入(inject)。 (非框架版本相当于“将对象传递给需要它的东西”。:P)我去设计了上面的东西只是为了尝试证明自己在单例和继承方面是错误的,只是向自己重申了多么邪恶组合是。我不建议在真正的代码中真正做到这一点。

    关于C++ 单例类——继承的好习惯,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9370749/

    相关文章:

    C++:在显示一些数据时避免 setter/getter

    c# - 单例构造函数是幂等的吗?

    c++ - 私有(private)和公共(public)职能

    php - 为什么不是 "instantiate a new object inside object constructor"?

    c++ - C++ 中的比较器函数的含义和工作原理?

    c++ - if block 的同步,C++

    java - 使用 Spring Boot 的 3 层架构最佳实践

    Javascript函数

    python - 如何使用mingw-w64,Python和pybind11手动构建C++扩展?

    c++ - 是否保证 sizeof(std::atomic<integer type>) == sizeof(integer type)?