Tuesday 16 January, 2007

Implementing a "final" class in C++

The key word "final" exists in java world. It means, no class can inherit from the class declared as "final". A class which cannot be inherited further.

The simple method by which we can achieve this is to make the constructor of the class private.


class CFinal
{
    private:
        CFinal(); // Private constructor to avoid inheritance

    public:
        static CFinal *GetInstance();


       // static public member function to create the instance
};


This method looks very simple. If we try to derive another class from CFinal and try to instantiate that class, compiler errors out saying cant access CFinal's constructor, since its private.

But, this method has its own drawbacks,


  • Need to have a static public member function to create instance.

  • If the class needs to have more than one type of c'tors, all these c'tors need to be replaced by corresponding version of static-public-member function.

  • Object cannot be instantiated on stack.

What if we make destructor private, instead of c'tors?
We can achieve the "final" thing.
But, this too cannot be instantiated on stack (stack-unwinding needs d'tors to be public).


We can club private d'tors, private c'tors, friend-class and virtual inheritance concepts to achieve a good solution.


class CFinal;  // Forward declaration

class CFinalLocker
{
    private:
        CFinalLocker();
        ~CFinalLocker();

    friend class CFinal;
};

class CFinal : public virtual CFinalLocker
{
    ...
};

class CDerived : public CFinal
{
    ...
}

CFinal objFinal; //OK - no problem
CDerived objDer; // Error! - Cant access CFinalLocker's c'tor


Following is the useful explanation of the above technique


  • CFinalLocker which is the base class for CFinal will have all of its c’tors and the destructor private.

  • CFinal has been made friend of CFinalLocker. This is to facilitate CFinal to access all the members of CFinalLocker including members which are private. This will allow us to instantiate CFinal.

  • We inherit our CFinal virtually from CFinalLocker. Now whenever anyone attempts to inherit class from CFinal and try to instantiate it, then its constructor tries to call the constructor of CFinalLocker. But the constructor of Temp is private so compiler complains about this and it gives error during the compilation, because your derived class is not friend of CFinalLocker. Remember friendship is not inherited in the derived class.

  • The purpose of inheriting CFinal virtually from CFinalLocker : the object of most derived class (whose object is created) directly calls the constructor of virtual base class (to avoid diamond problem). Now the most derived class (Bottom in case of diamond problem) directly calls the most base class (Top in diamond problem) constructor. So when we try instantiating CDerived compiler errors out since the c’tor in CFinalLocker is private.

7 comments:

manju said...

awesome explaination

Sunil said...

Hi Prashant
I came across this technique elsewhere in internet. However it still has a drawback - the compiler is silent till the time you instantiate the derived class. You can still inherit and have static content. I am thinking over it for a better solution but in the meanwhile you think of anything then let me know

Also, I am a newbie blogger. Wish to gtalk with you for tips (if you dont mind)

Anonymous said...

Neat..Good...

Ashish said...

is there a way we can implement CFinalLocker as a template . . .

if anyone is aware of that plz share . .

Prashant Bale said...

Gr8 :)

Anonymous said...

Like it!!!
-Ashish

Anonymous said...

Hello,
have a look at the upcoming C++0x standard. Now you can achive your goal in such a simple way:

struct Base final { };

struct Derived : Base { };

With GCC 4.7 you get:
cannot derive from »final« base »Base« in derived type »Derived«

Greetings from Rottenburg,
Rainer