C++11 introduced override as a contextual keyword. Matthew Wilson encourages us to use it.
TL;DR:
Reduce opacity in C++ inheritance hierarchies w
override
k/w (or comments in C++03/earlier)
Bite:
One of the myriad sources of confusion about C++’s ambiguous syntax is in determining whether or not a virtual function is one prescribed in the type one is currently examining or prescribed from a parent class. Consider the following:
class MidLevel : public TopLevel { . . . virtual void SomeMethod(); };
There are several possible interpretations:
-
The virtual method
SomeMethod()
is defined and implemented only within the classMidLevel
(and may be overridden by derived types);? -
The virtual method
SomeMethod()
is defined by the classTopLevel
(or one of its ancestors) in which it is pure, soMidLevel::SomeMethod()
is (at this level) its one and only definition;? -
The virtual method
SomeMethod()
is defined and implemented by the classTopLevel
(or one of its ancestors) soMidLevel::SomeMethod()
is an override (which may or may not invokeTopLevel::SomeMethod()
in its implementation);?
The only way to know is to examine the definition of the class
TopLevel
, or the definition of (a) parent class of
TopLevel
, or the definition of (a) grandparent class of
TopLevel
, or …
Application of the C++ 11 keyword override is a boon to discoverability, in that it connotes that the method is an override of a method declared/defined by a parent class. Hence, Listing 1 implies either case 2 or 3 above.
class MidLevel : public TopLevel { . . . virtual void SomeMethod() override; }; |
Listing 1 |
With C++-98/03 (or any compiler that does not support C++ 11’s override), an alternative declarative technique is simply to use a comment, as in Listing 2, or object-like pseudo-keyword macro (that resolves to
override
with compilers that support the keyword, and to nothing with those that do not), as in Listing 3 and Listing 4.
class MidLevel : public TopLevel { . . . virtual void SomeMethod() /* override */; }; |
Listing 2 |
#ifdef ACMESOFT_COMPILER_SUPPORTS_override_KEYWORD # define ACMESOFT_override_K override #else # define ACMESOFT_override_K #endif |
Listing 3 |
class MidLevel : public TopLevel { . . . virtual void SomeMethod() ACMESOFT_override_K; }; |
Listing 4 |
Of course, the virtue of the new keyword is far greater than that of connotation of design intent – it facilitates enforcement of design intent. If Listing 1 is presented to the compiler in case 1, it will be a compiler error, since one cannot override something that does not (previously) exist. That’s great.
Just as importantly, it guards against coding errors and/or design changes, in requiring that the overriding method matches the overridden method. If Listing 1 compiles, but then the method signature (or return type, or cv-qualification, or universality) changes in the parent class – the fragile base class problem – it will be a compile error. That’s great too.
(Obviously, neither the comment-form nor the macro-form do any enforcement with non-C++-11-compatible compilation, but it still is a non-trivial amount of help for the human side of things.)
Prior to the advent of
override
my practice was to distinguish between case 1 and cases 2/3 by placing the
virtual
keyword in comments, as in Listing 5.
class MidLevel : public TopLevel { . . . /* virtual */ void SomeMethod() ACMESOFT_override_K; }; |
Listing 5 |
Should you wish, you may do this too, but since
override
does a superior job in connoting overriding, you might prefer to elide it completely, as in Listing 6.
class MidLevel : public TopLevel { . . . void SomeMethod() ACMESOFT_override_K; }; |
Listing 6 |
After all this you might be wondering what we do about distinguishing between cases 2 and 3. That’s another story (but if I give you a hint and suggest that case 3 is pretty much a design smell, pertaining to modularity as well as discoverability , you might get there before we visit that subject in this place).