Click here to Skip to main content
Click here to Skip to main content

Sealing Classes in C++

By , 2 Sep 2009
 

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; // error C2248 

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; // OK; no compiler error 

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; // error C2248

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

  1. C++ Q&A: List View Mode, SetForegroundWindow, and Class Protection
  2. Vladislav Lazarenko: "[boost] Sealed C++ class"

History

  • 2nd September, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The MIT License

About the Author

FrancisXavier
Software Developer
India India
Member
Francis Xavier likes to watch sci-fi/fantasy movies, listen to rock music, play video-games, and hang out with his better half. 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.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionThanks Francismemberconsthab210 Apr '13 - 3:44 
Good job... I not need this stuff, but I need th __COUNTER___. In this more about the macro
http://strongcpp.blogspot.ru
BugPortable solution flaw [modified]memberYaroslav Lobachevski6 Dec '12 - 12:52 
Portable solution can be hacked by:
class BigZ : Penguin, Sealed (Penguin)
{
};
And here is the fix:
template <class T>
class SealedBase
{
friend typename T;
private:
    SealedBase()
    {
    }
};


modified 6 Dec '12 - 18:59.

GeneralThe soluion does not seem simplememberMukit, Ataul20 Feb '11 - 7:02 
Just to make a class sealed (specially in C++), to go through such hassel destroys the simple is beautiful concept of C++. Keeping the constructor private seems good enough solution.
 
Better still, just use a comment /*sealed*/ before the class declaration, and the class name (which should reflect its purpose) along with the sealed comment should be enough for sensible programmers.
 
But as a food for thought, your article is interesting..
GeneralSealed class need does existmemberBhushan198023 Feb '10 - 19:27 
To all those who are constantly negating the necessity of a sealed class, I would say that perhaps you were not into that kind of situation. And, I can also understand that it is not very good idea to make a class a sealed class. atleast in C++, in general or common place scenarios. But, there are situations as explained by the EventClass Example somewhere along this thread. The class could have been kept unsealed, but as said, making it sealed helps compiler make optimizations, which is a prime necessity of a C++ compiler. C++ compilers make numerous optimizations for template classes as well. Secondly, developers experienced in designing frameworks do consider numerous situations, where in they can estimate the misuse of the class and think about the ways to avoid them. They also consider the impact of the extensibility and do a break even of the benefits achieved by the extend versus the optimized code that is being developed. If the memory leaks, buffer over runs, v table allocations are not optimizable and the derived or inherited class benefits are not substantial, then it is always better to keep a class sealed and let compiler not bother about optimizing further classes in its hierarchy. I am sure there would be others who would defy my arguments...and I would like someone to come up with a better solution for the case where a class is inherited from multiple base classes with protected constructors. The creator of the thread mentioned the flaw that he can still inherit from 2 base classes, if one was a parent class with protected constructor...
BTW, I really do appreciate this approach
Thanks for reading
Bhushan
GeneralSimplier way to make a class sealed membersergey nazarov8 Sep '09 - 1:37 
Just make the base's default constructor private and you'll gain the same feature.
GeneralRe: Simplier way to make a class sealedmemberemilio_grv9 Sep '09 - 22:26 
Not only the "default". ALL the constructors, otherwise I can circumvent by calling explicitly another one from the derived constructor's initialization list.
 

2 bugs found.
> recompile ...
65534 bugs found.
D'Oh! | :doh:


GeneralRe: Simplier way to make a class sealedmemberBhushan198023 Feb '10 - 19:05 
Not if there is a case of multiple inheritance as in case of Penguin and SealedBase in this example. I mean if there is a multiple inheritance only one constructor is invoked and that is Base class constructor. Also, by making all the constructors private, you might make a class sealed, but it is useless. By using this method, a developer can still access the base class methods and other classes in the hierarchy without inheriting the sealed class any further. If the constructor was private, there would not be any access to the base member functions by the way of sealed classes' instance created, which I believe is the sole purpose of the sealed class...
QuestionWhat is the purpose of a Sealed Class?memberhector santos2 Sep '09 - 18:03 
What is this used for? Why is it needed? What does it cure? What does it solve? What does anyone need a Sealed Class?
 
--
AnswerRe: What is the purpose of a Sealed Class?memberTim Craig2 Sep '09 - 18:25 
It's pretty useless. If you publish your classes, why would I want to use them if I can't inherit and modify and/or embellish? If you don't publish, it serves no purpose at all unless you're saying there's no possible change that can be made and you want to discourage anyone from trying.
 
You measure democracy by the freedom it gives its dissidents, not the freedom it gives its assimilated conformists.

GeneralRe: What is the purpose of a Sealed Class?memberemilio_grv4 Sep '09 - 2:42 
Tim, the world is not so... boolean!
The purpose to prohibit things that are not supposed to happen has in general two main goals:
  1. Prevent mistakes
  2. Give chances to more optimizations
 
In this specific case, even if I'm writing for myself, there could be things that by design I don't want to do I can forget about later. By having a mechanism that forces me into a compile error makes me wonder abut what the problem is.
 
And -in any case- I can public a class because I want you to create object instances but not new object types (may be because the internal functionality of the class may change in the future, and I don't want to risk to create supportability problems, or because the "class" is just a language way to expose a functionality that's not internally a class (so whatever derivation mechanism will simply not work as intended by a language's users)
 
Also, If I know that a class will never be derived, letting the compiler to know can open to it more chances in optimizing the code (that's not the case of C++, the trick explained here give not evidence of that to the compiler),
But if you think to Java, C#, D and other languages where "sealed" is a keyword (so the compiler really knowns about it), that could mean to the compiler "you can discard any V-table mapping export into an import library since I grant nobody else will ever touch this". And that's are all implicit pointers that are not encoded and redirection that are skipped away.
 
Hope this may give you a more wide perspective.
 

2 bugs found.
> recompile ...
65534 bugs found.
D'Oh! | :doh:


General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 2 Sep 2009
Article Copyright 2009 by FrancisXavier
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid