Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Singleton class - inheritance good practice

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!

like image 790
kiriloff Avatar asked Feb 21 '12 01:02

kiriloff


People also ask

Should you avoid singletons?

The most important drawback of the singleton pattern is sacrificing transparency for convenience. Consider the earlier example. Over time, you lose track of the objects that access the user object and, more importantly, the objects that modify its properties.

Can singleton class be inherited?

Unlike static classes, Singleton classes can be inherited, can have base class, can be serialized and can implement interfaces. You can implement Dispose method in your Singleton class.

What is the best way to subclass singletons?

A singleton class has a private constructor which is not accessible to any other class inside the same package or outside. Hence it cannot be sub classed from any other class.

Are singletons necessary?

A singleton should be used when managing access to a resource which is shared by the entire application, and it would be destructive to potentially have multiple instances of the same class. Making sure that access to shared resources thread safe is one very good example of where this kind of pattern can be vital.


1 Answers

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

And in your init code, you say Base::initInstance(); or Derived::initInstance();, depending on which type you want the singleton to be. You'll have to cast the return value from Base::getInstance() 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.

like image 165
cHao Avatar answered Sep 23 '22 16:09

cHao