How to Define Sealed Class in C++

How to define sealed class in C++?

C++11 solution

In C++11, you can seal a class by using final keyword in the definition as:

class A final  //note final keyword is used after the class name
{
//...
};

class B : public A //error - because class A is marked final (sealed).
{ // so A cannot be derived from.
//...
};

To know the other uses of final, see my answer here:

  • What is the purpose of the "final" keyword in C++11 for functions?


C++03 solution

Bjarne Stroustrup's code : Can I stop people deriving from my class?

class Usable;
class Usable_lock {
friend class Usable;
private:
Usable_lock() {}
Usable_lock(const Usable_lock&) {}
};

class Usable : public virtual Usable_lock {
public:
Usable();
Usable(char*);
};
Usable a;

class DD : public Usable { };

DD dd; // error: DD::DD() cannot access
// Usable_lock::Usable_lock(): private member


Generic_lock

So we can make use of template to make the Usable_lock generic enough to seal any class:

template<class T>
class Generic_lock
{
friend T;
Generic_lock() {} //private
Generic_lock(const Generic_lock&) {} //private
};

class Usable : public virtual Generic_lock<Usable>
{
public:
Usable() {}
};

Usable a; //Okay
class DD : public Usable { };

DD dd; //Not okay!

What is an internal sealed class in C#?

It is a class that:

  • internal: Can only be accessed from within the assembly it is defined (or friend assemblies).
  • sealed: Cannot be inherited.

Marking classes as internal is a way of preventing outside users of an assembly from using them. It's really a form of design encapsulation and IMHO it is good practice to mark types that are not part of the intended public API\object models as internal. In the long term this prevents users of your library from coupling themselves to types which you did not intend them to. This sort of unintended coupling harms your ability to change and evolve the way your libraries are implemented as you cannot change them without breaking your clients. Using internal helps to keep the public and usable surface area of a library down to what is intended.

Marking classes as sealed prevents these classes from being inherited. This is a pretty drastic design intent which is sometimes useful if a class is already so specialized that it is sensible that no other functionality should be added to it via inheritance either directly or via overriding its behaviour.

internal and sealed modify types in quite different ways, but they can be used together.

NB You have some further scoping control of internal as you can define a set of other assemblies as 'friends'. These friend assemblies may access your internal types. This can be useful for defining sets of co-operating assemblies such as production and test assemblies. It is often desirable that a test assembly can see all the types in the assembly it is testing.

Why do we need sealed classes?

First of all, let's start with a definition; sealed is a modifier which if applied to a class make it non-inheritable and if applied to virtual methods or properties makes them non-ovveridable.

public sealed class A { ... }
public class B
{
...
public sealed string Property { get; set; }
public sealed void Method() { ... }
}

An example of its usage is to define a specialized class/method or property in which potential alterations can make them stop working as expected (for example, the Pens class of the System.Drawing namespace).

...
namespace System.Drawing
{
//
// Summary:
// Pens for all the standard colors. This class cannot be inherited.
public sealed class Pens
{
public static Pen Transparent { get; }
public static Pen Orchid { get; }
public static Pen OrangeRed { get; }
...
}
}

Because a sealed class cannot be inherited, it cannot be used as base class and by consequence, an abstract class cannot use the sealed modifier.

It's also important to mention that structs are implicitly sealed.

Performance

To really see them you need to analyze the JIT-Compiled code (last one).

C# Code

public sealed class Sealed
{
public string Message { get; set; }
public void DoStuff() { }
}
public class Derived : Base
{
public sealed override void DoStuff() { }
}
public class Base
{
public string Message { get; set; }
public virtual void DoStuff() { }
}
static void Main()
{
Sealed sealedClass = new Sealed();
sealedClass.DoStuff();
Derived derivedClass = new Derived();
derivedClass.DoStuff();
Base BaseClass = new Base();
BaseClass.DoStuff();
}

MIL Code

.method private hidebysig static void  Main() cil managed
{
.entrypoint
// Code size 41 (0x29)
.maxstack 8
IL_0000: newobj instance void ConsoleApp1.Program/Sealed::.ctor()
IL_0005: callvirt instance void ConsoleApp1.Program/Sealed::DoStuff()
IL_000a: newobj instance void ConsoleApp1.Program/Derived::.ctor()
IL_000f: callvirt instance void ConsoleApp1.Program/Base::DoStuff()
IL_0014: newobj instance void ConsoleApp1.Program/Base::.ctor()
IL_0019: callvirt instance void ConsoleApp1.Program/Base::DoStuff()
IL_0028: ret
} // end of method Program::Main

JIT- Compiled Code

--- C:\Users\Ivan Porta\source\repos\ConsoleApp1\Program.cs --------------------
{
0066084A in al,dx
0066084B push edi
0066084C push esi
0066084D push ebx
0066084E sub esp,4Ch
00660851 lea edi,[ebp-58h]
00660854 mov ecx,13h
00660859 xor eax,eax
0066085B rep stos dword ptr es:[edi]
0066085D cmp dword ptr ds:[5842F0h],0
00660864 je 0066086B
00660866 call 744CFAD0
0066086B xor edx,edx
0066086D mov dword ptr [ebp-3Ch],edx
00660870 xor edx,edx
00660872 mov dword ptr [ebp-48h],edx
00660875 xor edx,edx
00660877 mov dword ptr [ebp-44h],edx
0066087A xor edx,edx
0066087C mov dword ptr [ebp-40h],edx
0066087F nop
Sealed sealedClass = new Sealed();
00660880 mov ecx,584E1Ch
00660885 call 005730F4
0066088A mov dword ptr [ebp-4Ch],eax
0066088D mov ecx,dword ptr [ebp-4Ch]
00660890 call 00660468
00660895 mov eax,dword ptr [ebp-4Ch]
00660898 mov dword ptr [ebp-3Ch],eax
sealedClass.DoStuff();
0066089B mov ecx,dword ptr [ebp-3Ch]
0066089E cmp dword ptr [ecx],ecx
006608A0 call 00660460
006608A5 nop
Derived derivedClass = new Derived();
006608A6 mov ecx,584F3Ch
006608AB call 005730F4
006608B0 mov dword ptr [ebp-50h],eax
006608B3 mov ecx,dword ptr [ebp-50h]
006608B6 call 006604A8
006608BB mov eax,dword ptr [ebp-50h]
006608BE mov dword ptr [ebp-40h],eax
derivedClass.DoStuff();
006608C1 mov ecx,dword ptr [ebp-40h]
006608C4 mov eax,dword ptr [ecx]
006608C6 mov eax,dword ptr [eax+28h]
006608C9 call dword ptr [eax+10h]
006608CC nop
Base BaseClass = new Base();
006608CD mov ecx,584EC0h
006608D2 call 005730F4
006608D7 mov dword ptr [ebp-54h],eax
006608DA mov ecx,dword ptr [ebp-54h]
006608DD call 00660490
006608E2 mov eax,dword ptr [ebp-54h]
006608E5 mov dword ptr [ebp-44h],eax
BaseClass.DoStuff();
006608E8 mov ecx,dword ptr [ebp-44h]
006608EB mov eax,dword ptr [ecx]
006608ED mov eax,dword ptr [eax+28h]
006608F0 call dword ptr [eax+10h]
006608F3 nop
}
0066091A nop
0066091B lea esp,[ebp-0Ch]
0066091E pop ebx
0066091F pop esi
00660920 pop edi
00660921 pop ebp

00660922 ret

While the creation of the objects is the same, the instruction executed to invoke the methods of the sealed and derived/base class are slightly different. After moving data into registers or RAM (mov instruction), the invoke of the sealed method, execute a comparison between dword ptr [ecx],ecx (cmp instruction) and then call the method while the derived/base class execute directly the method..

According to the report written by Torbj¨orn Granlund, Instruction latencies and throughput for AMD and Intel x86 processors, the speed of the following instruction in a Intel Pentium 4 are:

  • mov: has 1 cycle as latency and the processor can sustain 2.5 instructions per cycle of this type
  • cmp: has 1 cycle as latency and the processor can sustain 2 instructions per cycle of this type

Link: https://gmplib.org/~tege/x86-timing.pdf

The optimization of the compilers have made the difference between the performances of a sealed and not-sealed classed so low that we are talking about processor circles and for this reason are irrelevant for the majority of applications.

When and why would you seal a class?

  1. On a class that implements security features, so that the original object cannot be "impersonated".

  2. More generally, I recently exchanged with a person at Microsoft, who told me they tried to limit the inheritance to the places where it really made full sense, because it becomes expensive performance-wise if left untreated.
    The sealed keyword tells the CLR that there is no class further down to look for methods, and that speeds things up.

In most performance-enhancing tools on the market nowadays, you will find a checkbox that will seal all your classes that aren't inherited.

Be careful though, because if you want to allow plugins or assembly discovery through MEF, you will run into problems.

Sealed method in C#

Well, you are testing with only two levels of inheritance, and you are not getting to the point that you're "further overriding" a method. If you make it three, you can see what sealed does:

class Base {
public virtual void Test() { ... }
}
class Subclass1 : Base {
public sealed override void Test() { ... }
}
class Subclass2 : Subclass1 {
public override void Test() { ... } // Does not compile!
// If `Subclass1.Test` was not sealed, it would've compiled correctly.
}

Forcing a sealed class

No, it is not possible.

The fact that the class is sealed simply means it can't be inherited.

For example something like: public interface A<SealedItem> where SealedItem : sealed

You can only use generic constraints for things that controls how a type can be used - if it's a class or a struct, if it implements an interface or is derived from a specific type or if it has a public constructor that takes no parameters.

or maybe something like: public sealed interface A {}

An interface can't be marked as sealed.

The one thing that you can do to prevent inheritance is to use value types - structs can't inherit from anything other than the ValueType special class (and that's beyond your control), nor can the be inherited from -

from Structs (C# Programming Guide):

A struct cannot inherit from another struct or class, and it cannot be the base of a class.

however this will probably not be a good choice in most cases, especially if you need reference type semantics in your code (and in most cases, you do).

Update - followed by an update to the question:

Extra context: I have request objects that are exposed through an API. These request objects may change over time, however, I don't want these request objects to influence each other.

You can create all your request classes as sealed - and leave comments in the code for future developments that requests should be sealed and explain why - but that's probably all you can do, other than using structs instead of classes (which might not be a bad idea, if it's only for API requests.)

Check out Choosing Between Class and Struct to see if your requests meets the guidelines:

✓ CONSIDER defining a struct instead of a class if instances of the type are small and commonly short-lived or are commonly embedded in other objects.

X AVOID defining a struct unless the type has all of the following characteristics:

  • It logically represents a single value, similar to primitive types (int, double, etc.).
  • It has an instance size under 16 bytes.
  • It is immutable.
  • It will not have to be boxed frequently.

In all other cases, you should define your types as classes.

Abstract Sealed Classes

It is equivalent to "static class" in the C# language. The language that was used to write almost all of the BCL classes. All the methods must be static. Declaring it abstract and sealed prevents anybody from deriving from the class and creating an instance of it.

The class methods are the exact equivalent of free functions in the C and C++ language. Something the CLR does not support.

Abstract class cannot be sealed in c#?

Having checked the IL of the System.Directory class (which is static), it is declared in IL as:

.class public auto ansi abstract sealed beforefieldinit System.IO.Directory
extends System.Object
{
...

Further, this article (http://msdn.microsoft.com/en-us/library/ms229038.aspx) suggests that the CLR handles static classes as abstract sealed classes to support languages that do not support directly delcaring static classes (eg C++).

Thus in conclusion, static classes in C# are syntactic sugar for sealed abstract classes with private constructors. I for one am glad of that as "static" is a lot easier to write and a lot easier to get right.



Related Topics



Leave a reply



Submit