Mit den kontextsensitiven Schlüsselwörtern override und final lässt sich explizit das Überschreiben von virtuellen Methoden kontrollieren. Insbesondere das Schlüsselwort override löst häufig vorkommende und schwer zu findende Probleme in Ableitungshierarchien. Methoden, die Methoden einer Basisklasse vermeintlich überschreiben. Das Ergebnis ist ein syntaktisch richtiges Programm, dass aber nicht das gewünschte Verhalten an den Tag legt.
override
Um eine Methode richtig zu überschreiben, muss deren Signatur exakt stimmen. Was einfach klingt, gestaltet sich in der Praxis tückisch: Passt die Methode nicht exakt, verhält sich das Programm zwar syntaktisch richtig, doch die Semantik stimmt nicht, denn statt der gewünschten Methode wird eine andere Methode aufgerufen.
Der Bezeichner override in der Methodendeklaration drückt aus, dass die Methode eine virtuelle Methode einer der Basisklassen überschreiben soll. Diese Zusicherung prüft der Compiler. Dabei berücksichtigt er die Parameter der Methode, deren Rückgabetyp und Qualifier wie const oder volatile. Ihm fällt natürlich auch auf, wenn die überschriebene Methode nicht virtuell ist.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
// override.cpp
class Base {
void func1();
virtual void func2(float);
virtual void func3() const;
virtual long func4(int);
virtual void f();
};
class Derived: public Base {
virtual void func1() override;
virtual void func2(double) override;
virtual void func3() override;
virtual int func4(int) override;
void f() override;
};
int main(){
Base base;
Derived derived;
};
|
Wird das Programm übersetzt, hat der Compiler einiges zu monieren und bringt es deutlich auf den Punkt.
In Zeile 16 stört ihn, dass die Methode func1 keine Methode überschreibt. Das gleich gilt für die Methode func2. Sie besitzt den falschen Parametertyp. Weiter geht es mit der Methode func3. Ihr fehlt der const Qualifier. func4 hingegen besitzt den falschen Rückgabetyp. Lediglich die Funktion f in Zeile 24 überschreibt die Methode ihrer Basisklasse.
Soll eine virtuelle Methode nicht überschrieben werden, ist dies ein Anwendungsfall für final.
final
Durch das neue Schlüsselwort final lässt sich sowohl eine Methode deklarieren, die nicht überschrieben werden darf, als auch eine Basisklasse deklarieren, von der nicht abgeleitet werden kann. Um zu bestimmen, ob eine Methode eine virtuelle Methode einer Basisklasse überschreibt, wendet der Compiler bei final die gleichen Regeln wie bei override an. Natürlich ist die Strategie auf den Kopf gestellt, da final das Überschreiben einer Methode unterbinden soll. So prüft der Compiler die Parameter der Methode, deren Rückgabetyp und die const/volatile Qualifier.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
// final.cpp
class Base {
virtual void h(int) final;
virtual void g(long int);
};
class Derived: public Base {
virtual void h(int);
virtual void h(double);
virtual void g(long int) final;
};
class DerivedDerived: public Derived {
virtual void g(long int);
};
struct FinalClass final { };
struct DerivedClass: FinalClass { };
int main(){
Base base;
Derived derived;
DerivedDerived derivedDerived;
FinalClass finalClass;
DerivedClass derivedClass;
};
|
Was passiert nun beim Übersetzen des Programms?
Der Compiler erledigt seinen Job sehr ordentlich. So moniert er, dass die Methode h in der Klasse Base (Zeile 4) durch die Methode in der Klasse Derived (Zeile 9) überschrieben wird. Hingegen ist es zulässig, die Methode h (Zeile 10) in der Klasse Derived mit dem Parametertyp double zu überladen. Interessant ist vor allem die Methode g in der Klasse Derived (Zeile 11). Sie überschreibt die Methode g der Klasse Base (Zeile 5) und deklariert sie zusätzlich als final. Damit kann sie in der Klasse DerivedDerived (Zeile 15) nicht mehr überschrieben werden. Zuletzt noch die Klasse FinalClass (Zeile 18). DerivedClass kann von ihr nicht ableiten, da sie als final deklariert ist.
Ein Kleinigkeit habe ich bis jetzt willentlich ignoriert. Die Schlüsselwörter override und final sind kontextsensitive Schlüsselwörter. Was heißt das?
Kontextsensitive Schlüsselwörter
override und final sind nur in bestimmten Kontexten Schlüsselwörter. Diese Kontexte sind das Deklarieren einer Methode oder einer Klasse. Werden sie in anderen Zusammenhängen verwendet, sind sie nur gewöhnliche Bezeichner. Warum hat sich der Standard dazu entschieden, kontextsensitive Schlüsselwörter einzuführen? Zum einen widerstrebt es dem Standardisierungskomitee, immer neue Schlüsselwörter einzuführen, zum andern und vor allem bleiben klassische C++-Programme mit dem C++11-Standard gültig, die die kontextsensitive Schlüsselwörter override und final verwenden. Den ein wichtiges Prinzip von C++ lautet: Brich keinen funktionierenden Code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// keywords.cpp
#include <iostream>
void override(){ std::cout << "override" << std::endl; }
int main(){
std::cout << std::endl;
override();
auto final(2011);
std::cout << "final: " << final << std::endl;
std::cout << std::endl;
}
|
Sowohl steht es mir in C++11 frei, die Funktion override (Zeile 5) zu nennen, als auch, die Variable den Namen final (Zeile 13) zu geben.
Nur der Vollständigkeit halber. Mit default und delete kennt C++11 weitere kontextsensitive Schlüsselwörter.
Wie geht's weiter?
Das neue Schlüsselwort nullptr definiert eine Nullzeigerkonstate in C++11. Damit räumt es mit der Mehrdeutigkeit der Zahl 0 in C++ und dem C-Makro NULL auf. Wie, das zeigt der nächste Artikel.
Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library". Hole dir dein E-Book. Unterstütze meinen Blog.
Weiterlesen...