ACCU Home page ACCU Conference Page
Search Contact us ACCU at Flickr ACCU at GitHib ACCU at Facebook ACCU at Linked-in ACCU at Twitter Skip Navigation

pinQM Bites : Maximising Discoverability of Virtual Methods

Overload Journal #131 - February 2016 + Programming Topics   Author: Matthew Wilson
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:

  1. The virtual method SomeMethod() is defined and implemented only within the class MidLevel (and may be overridden by derived types);?
  2. The virtual method SomeMethod() is defined by the class TopLevel (or one of its ancestors) in which it is pure, so MidLevel::SomeMethod() is (at this level) its one and only definition;?
  3. The virtual method SomeMethod() is defined and implemented by the class TopLevel (or one of its ancestors) so MidLevel::SomeMethod() is an override (which may or may not invoke TopLevel::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).

Overload Journal #131 - February 2016 + Programming Topics