Introduction
Some recent languages like C# and Java allow you to seal your classes easily using a keyword like sealed or final respectively. C++ doesn't have any such keyword for this purpose. However, it's possible to still do it using a trick. When using virtual inheritance, the initialization list of the most-derived-class's constructor directly invokes the virtual base class's constructor. This means that if we can hide access to the virtual base class's constructor, then we can prevent any class from deriving from it. This mimics the effect of being sealed.
Solution Attempt #1
To provide an easy way to seal classes, we can write a header file Sealed.h like this:
class SealedBase
{
protected:
SealedBase()
{
}
};
#define Sealed private virtual SealedBase
Now to seal a class, say Penguin
, we just need to derive it from Sealed
, like this:
#include "Sealed.h"
class Penguin : Sealed
{
};
That's it. Penguin
is now a sealed class. Let's try deriving another class, BigZ
(Surf's Up (2007), anyone?) from Penguin
:
class BigZ : Penguin
{
};
BigZ bigZ;
Instantiating an object of BigZ
should yield a compiler error. The MSVC++ 2005 compiler gives me the following error message:
error C2248: 'SealedBase::SealedBase' : cannot access inaccessible member
declared in class 'SealedBase'
A Serious Flaw
All seems to be working well. However, one of my fellow programmers, Angelo Rohit, pointed out to me that this method has a serious flaw in it. Angelo says that if BigZ
derives from Penguin
and Sealed
, then it will be possible to create objects of BigZ
:
class BigZ : Penguin, Sealed
{
};
BigZ bigZ;
Why does this happen? BigZ
derives from Sealed
just like Penguin
does, which means that it now has access to Sealed
's constructor. And since Sealed
is inherited virtually by both Penguin
and BigZ
, there is only one copy of it - which is now also accessible to BigZ
. Bummer. We need to have a mechanism by which BigZ
is forced to call the constructor of a class which it doesn't have access to.
Solution Attempt #2
After pondering over this for a while, I realized that if we can somehow generate different base classes every time Sealed
is derived from, then it would work.
Let's rewrite the Sealed.h header to look like this:
template <int T>
class SealedBase
{
protected:
SealedBase()
{
}
};
#define Sealed private virtual SealedBase<__COUNTER__>
What does this do? SealedBase
is now a templated class which takes an integer as an argument. __COUNTER__
is a predefined macro which expands to an integer starting with 0 and incrementing by 1 every time it is used in a compiland. So every time Sealed
is derived from, it generates a new SealedBase
class using the incremental number which __COUNTER__
expands to.
Now let's go back to our BigZ
class which derives from both Penguin
and Sealed
:
class BigZ : Penguin, Sealed
{
};
BigZ bigZ;
This time around though, BigZ
can't escape from the compiler. Penguin
derives from SealedBase<number1>
and BigZ
derives from SealedBase<number2>
, where number1
and number2
are two non-identical integers. So now BigZ
has to invoke the constructor of SealedBase<number1>
, which it doesn't have access to.
The MSVC++ 2005 compiler gives me the following error message:
error C2248: 'SealedBase<T>::SealedBase' : cannot access inaccessible member
declared in class 'SealedBase<T>'
1> with
1> [
1> T=0
1> ]
Portability Issues
However, you might be thinking that since we're using a special predefined macro __COUNTER__
in our implementation, this code is not portable. Well, it's supported by MSVC++ (which I used to test the above code) and also by GCC (http://www.gnu.org/software/gcc/gcc-4.3/changes.html).
But what about compilers which don't?
A Portable Solution
After a little thought, I came up with the following way:
In Sealed.h:
template <class T>
class SealedBase
{
protected:
SealedBase()
{
}
};
#define Sealed(_CLASS_NAME_) private virtual SealedBase<_CLASS_NAME_>
And to seal a class:
#include "Sealed.h"
class Penguin : Sealed(Penguin)
{
};
When sealing a class, we need to mention that class's name to the Sealed
macro. This enables the Sealed
macro to generate a new version of SealedBase
. This is less elegant than simply having to derive from Sealed
, but is more portable, making it a good alternative for compilers which don't support the __COUNTER__
predefined macro.
Final Words
People who use MSVC++ or GCC can simply use Solution Attempt #2, as it is cleaner. People on other compilers, can use the Portable Solution. If you have any questions, suggestions, improvements, or simply want to say hi, please email me.
Thanks for reading!
Francis Xavier
References
- C++ Q&A: List View Mode, SetForegroundWindow, and Class Protection
- Vladislav Lazarenko: "[boost] Sealed C++ class"
History
- 2nd September, 2009: Initial post
Besides loving spending time with family, Francis Xavier likes to watch sci-fi/fantasy/action/drama movies, listen to music, and play video-games. After being exposed to a few video-games, he developed an interest in computer programming. He currently holds a Bachelor's degree in Computer Applications.