C++ Singleton类——继承的良好实践

发布于 2025-01-07 02:58:45 字数 542 浏览 2 评论 0原文

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

首先,我会详细阐述这种继承的背景和需求。

我添加到现有软件中的应用程序想要使用 MVC 模块来执行与我愿意执行的任务几乎相同的任务。它使用相同的方法直到签名并进行轻微修改。重写我自己的 MVC 模块肯定会重复代码。现有模块本质上是面向其应用于软件的另一部分的,我不能简单地使用相同的模块。但被编写为模型-视图-控制器模式,其中控制器是单例。我已经导出了 View 。

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

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

第三,这是我如何看待一些处理的方法。请评论/帮助我改进!

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

谢谢!

In an existing project, I am to inherit a Controller class (MVC) declared as Singleton so as to define my own treatment. How to appropriately derive this Singleton class?

First, I expand on context and need for this inheritance.

The application that I am added to the existing software wants to use a MVC module that performs almost same task as the one I am willing to perform. It is using the same methods up to signature and slight modifications. Rewriting my own MVC module would definitively be duplication of code. The existing module is intrinsically oriented towards its application to another part of the software, and I cannot simply use the same module. But is written as a Model-View-Controller pattern where Controller is Singleton. I derived View already.

Second, I have doubt that I can classicaly derive Singleton class.

Calling constructor from inherited class would simply call getinstance() for parent class and fail to return an object from derived class (?).

Third, it's how I see some way to deal with. Please comment/help me improve!

I copy the whole Singleton class in a class I could call AbstractController. I derive this class twice. The first child is singleton and adopts the whole treatment of parent class. The second child is the Controller for my part of the application, with own redefined treatment.

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

玩心态 2025-01-14 02:58:45

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

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

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

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

  • 我们可以创建一个Base并返回它。螺丝衍生其他。最终结果:Base::getInstance() 始终返回准确的Base。孩子们的班级从来没有玩过。在我看来,有点违背了目的。

  • 我们可以将我们自己的 getInstance 放入派生类中,并让调用者说 Derived::getInstance()(如果他们特别想要一个 Derived)。这显着增加了耦合性(因为调用者现在必须知道具体请求 Derived,并最终将自己绑定到该实现)。

  • 我们可以做最后一个的变体——但是函数不获取实例,而是创建一个实例。 (此时,让我们将该函数重命名为 initInstance,因为我们并不特别关心它会得到什么——我们只是调用它,以便它创建一个新的 Derived 并将其设置为一个真实实例。)

所以(除非有任何未解释的奇怪情况),它的工作原理有点像这样......

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 定义的任何函数,包括 virtual被Derived覆盖的函数。

请注意,这种方法本身也有许多缺点:

  • 它将强制单一性的大部分负担放在基类上。如果底座没有这个或类似的功能,而且你又不能改变它,那你就完蛋了。

  • 但是,基类不能承担全部的责任——每个类都需要声明一个受保护的析构函数,否则有人可能会在将其强制转换(传入)后删除该实例。适当地,整个事情就会变得糟糕。更糟糕的是,编译器无法强制执行此操作。

  • 因为我们使用受保护的析构函数来防止一些随机的笨蛋删除我们的实例,除非编译器比我担心的更聪明,否则即使运行时也无法在程序结束时正确删除您的实例。再见,RAII...你好“检测到内存泄漏”警告。 (当然,内存最终会被任何像样的操作系统回收。但是如果析构函数没有运行,你就不能依赖它来为你进行清理。你需要在你之前调用某种清理函数退出,这不会给您提供与 RAII 相同的保证。)

  • 它公开了一个 initInstance 方法,在我看来,该方法并不真正属于每个人都可以使用的 API看。如果您愿意,您可以将 initInstance 设为私有,并让您的 init 函数成为 friend,但随后您的类会对自身外部的代码做出假设,并且耦合问题又回来了进入游戏。

另请注意,上面的代码根本不是线程安全的。如果你需要的话,你就得靠自己了。

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

Truth is, singletons and inheritance do not play well together.

Yeah, yeah, the Singleton lovers and GoF cult will be all over me for this, saying "well, if you make your constructor protected..." and "you don't have to have a getInstance method on the class, you can put it...", but they're just proving my point. Singletons have to jump through a number of hoops in order to be both a singleton and a base class.

But just to answer the question, say we have a singleton base class. It can even to some degree enforce its singleness through inheritance. (The constructor does one of the few things that can work when it can no longer be private: it throws an exception if another Base already exists.) Say we also have a class Derived that inherits from Base. Since we're allowing inheritance, let's also say there can be any number of other subclasses of Base, that may or may not inherit from Derived.

But there's a problem -- the very one you're either already running into, or will soon. If we call Base::getInstance without having constructed an object already, we'll get a null pointer. We'd like to get back whatever singleton object exists (it may be a Base, and/or a Derived, and/or an Other). But it's hard to do so and still follow all the rules, cause there are only a couple of ways to do so -- and all of them have some drawbacks.

  • We could just create a Base and return it. Screw Derived and Other. End result: Base::getInstance() always returns exactly a Base. The child classes never get to play. Kinda defeats the purpose, IMO.

  • We could put a getInstance of our own in our derived class, and have the caller say Derived::getInstance() if they specifically want a Derived. This significantly increases coupling (because a caller now has to know to specifically request a Derived, and ends up tying itself to that implementation).

  • We could do a variant of that last one -- but instead of getting the instance, the function just creates one. (While we're at it, let's rename the function to initInstance, since we don't particularly care what it gets -- we're just calling it so that it creates a new Derived and sets that as the One True Instance.)

So (barring any oddness unaccounted for yet), it works out kinda like this...

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
};

In your init code, you say Base::initInstance(); or Derived::initInstance();, depending on which type you want the singleton to be, and when you want a pointer to the singleton, you call Base::getInstance(). You'll have to cast the return value in order to use any Derived-specific functions, of course, but without casting you can use any functions defined by Base, including virtual functions overridden by Derived.

Note that this way of doing it also has a number of drawbacks of its own, though:

  • It puts most of the burden of enforcing singleness on the base class. If the base doesn't have this or similar functionality, and you can't change it, you're kinda screwed.

  • The base class can't take all of the responsibility, though -- each class needs to declare a protected destructor, or someone could come along and delete the one instance after casting it (in)appropriately, and the whole thing goes to hell. What's worse, this can't be enforced by the compiler.

  • Because we're using protected destructors to prevent some random schmuck from deleting our instance, unless the compiler's smarter than i fear it is, even the runtime won't be able to properly delete your instance when the program ends. Bye bye, RAII...hello "memory leak detected" warnings. (Of course the memory will eventually be reclaimed by any decent OS. But if the destructor doesn't run, you can't depend on it to do cleanup for you. You'll need to call a cleanup function of some sort before you exit, and that won't give you anywhere near the same assurances that RAII can give you.)

  • It exposes an initInstance method that, IMO, doesn't really belong in an API everyone can see. If you wanted, you could make initInstance private and let your init function be a friend, but then your class is making assumptions about code outside itself, and the coupling thing comes back into play.

Also note that the code above is not at all thread safe. If you need that, you're on your own.

Seriously, the less painful route is to forget trying to enforce singleness. The least complicated way to ensure that there's only one instance is to only create one. If you need to use it multiple places, consider dependency injection. (The non-framework version of that amounts to "pass the object to stuff that needs it". :P ) I went and designed the above stuff just to try and prove myself wrong about singletons and inheritance, and just reaffirmed to myself how evil the combination is. I wouldn't recommend ever actually doing it in real code.

奢欲 2025-01-14 02:58:45

我不确定我是否完全理解您正在处理的情况,并且是否可能或是否适合从单例派生很大程度上取决于单例的实现方式。

但是,既然您提到了“良好实践”,那么在阅读问题时就会想到一些一般性的观点:

  1. 继承通常不是实现代码重用的最佳工具。请参阅:更喜欢组合而不是继承?

  2. 使用单例和“良好实践”通常不会一起去吧!请参阅:单例有什么不好?

希望有所帮助。

I'm not sure I understand the situation you're dealing with fully, and whether or not it's possible or appropriate to derive from the singleton depends very much on how the singleton is implemented.

But since you mentioned "good practice" there's some general points that come to mind when reading the question:

  1. Inheritance isn't usually the best tool to achieve code re-use. See: Prefer composition over inheritance?

  2. Using singleton and "good practice" generally do not go together! See: What is so bad about singletons?

Hope that helps.

苍风燃霜 2025-01-14 02:58:45

我最近在我的应用程序中有类似的需求......无论如何,这是我的代码实现:

h。

    class icProjectManagerHandler;
    class icProjectManager : public bs::icBaseManager {
        friend class icProjectManagerHandler;
    protected:
        icProjectManager();
    public:
        ~icProjectManager();

        template<typename t>
        static t *PM() {
            return dynamic_cast<t *>(icProjectManagerHandler::PMH()->mCurrentManager);
        };
        template<typename t>
        static t *PMS() {
            static icProjectManagerHandler pm;
            return static_cast<t *>(icProjectManagerHandler::PMH()->mCurrentManager);
        };
    };

    class icProjectManagerHandler {
        friend class icProjectManager;
        icProjectManager *mCurrentManager;
        icProjectManagerHandler();
    public:
        ~icProjectManagerHandler();
        static icProjectManagerHandler *PMH();

        inline void setProjectManager(icProjectManager *pm) {
            if (mCurrentManager) { delete mCurrentManager; }
            mCurrentManager = pm;
        }
    };

Cpp。

    icProjectManagerHandler::icProjectManagerHandler() {
        mCurrentManager = new icProjectManager();
    }
    icProjectManagerHandler::~icProjectManagerHandler() {

    }
    icProjectManagerHandler *icProjectManagerHandler::PMH() {
        static icProjectManagerHandler pmh;
        return &pmh;
    }
    icProjectManager::icProjectManager() {

    }
    icProjectManager::~icProjectManager() {

    }

示例:

class icProjectX : public ic::project::icProjectManager {
public:
    icProjectX() {};
    ~icProjectX() {};
};

int main(int argc, char *argv[]) {
    auto pro = new icProjectX();
    pro->setIcName("Hello");
    ic::project::icProjectManagerHandler::PMH()->setProjectManager(pro);
    qDebug() << "\n" << pro << "\n" << ic::project::icProjectManager::PMS<icProjectX>();
    return 10;
}

此实现的问题是您必须首先初始化“单例”类,否则您将获得默认基类。但除此之外...它应该有效吗?

I recently had the similar need in my app... in any case here is my out of code implementation :

h.

    class icProjectManagerHandler;
    class icProjectManager : public bs::icBaseManager {
        friend class icProjectManagerHandler;
    protected:
        icProjectManager();
    public:
        ~icProjectManager();

        template<typename t>
        static t *PM() {
            return dynamic_cast<t *>(icProjectManagerHandler::PMH()->mCurrentManager);
        };
        template<typename t>
        static t *PMS() {
            static icProjectManagerHandler pm;
            return static_cast<t *>(icProjectManagerHandler::PMH()->mCurrentManager);
        };
    };

    class icProjectManagerHandler {
        friend class icProjectManager;
        icProjectManager *mCurrentManager;
        icProjectManagerHandler();
    public:
        ~icProjectManagerHandler();
        static icProjectManagerHandler *PMH();

        inline void setProjectManager(icProjectManager *pm) {
            if (mCurrentManager) { delete mCurrentManager; }
            mCurrentManager = pm;
        }
    };

Cpp.

    icProjectManagerHandler::icProjectManagerHandler() {
        mCurrentManager = new icProjectManager();
    }
    icProjectManagerHandler::~icProjectManagerHandler() {

    }
    icProjectManagerHandler *icProjectManagerHandler::PMH() {
        static icProjectManagerHandler pmh;
        return &pmh;
    }
    icProjectManager::icProjectManager() {

    }
    icProjectManager::~icProjectManager() {

    }

And example:

class icProjectX : public ic::project::icProjectManager {
public:
    icProjectX() {};
    ~icProjectX() {};
};

int main(int argc, char *argv[]) {
    auto pro = new icProjectX();
    pro->setIcName("Hello");
    ic::project::icProjectManagerHandler::PMH()->setProjectManager(pro);
    qDebug() << "\n" << pro << "\n" << ic::project::icProjectManager::PMS<icProjectX>();
    return 10;
}

The issue of this implementation is that you have to initialize your "singleton" class 1st or else you will get the default base class. But other than that... it should work?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文