Virtual functions are a pretty basic feature, but they occasionally harbor subtleties that trap the unwary. If you can answer questions like this one, then you know virtual functions cold, and you’re less likely to waste a lot of time debugging problems like the ones illustrated below.



Problem

JG Question

1. What do the override and final keywords do? Why are they useful?

Guru Question

2. In your travels through the dusty corners of your company’s code archives, you come across the following program fragment written by an unknown programmer. The programmer seems to have been experimenting to see how some C++ features worked.

(a) What could be improved in the code’s correctness or style?

(b) What did the programmer probably expect the program to print, but what is the actual result?

class base { public: virtual void f( int ); virtual void f( double ); virtual void g( int i = 10 ); }; void base::f( int ) { cout << "base::f(int)" << endl; } void base::f( double ) { cout << "base::f(double)" << endl; } void base::g( int i ) { cout << i << endl; } class derived: public base { public: void f( complex<double> ); void g( int i = 20 ); }; void derived::f( complex<double> ) { cout << "derived::f(complex)" << endl; } void derived::g( int i ) { cout << "derived::g() " << i << endl; } int main() { base b; derived d; base* pb = new derived; b.f(1.0); d.f(1.0); pb->f(1.0); b.g(); d.g(); pb->g(); delete pb; }

Solution

1. What do the override and final keywords do? Why are they useful?

These keywords give explicit control over virtual function overriding. Writing override declares the intent to override a base class virtual function. Writing final makes a virtual function no longer overrideable in further-derived classes, or a class no longer permitted to have further-derived classes.

They are useful because they let the programmer explicitly declare intent in a way the language can enforce at compile time. If you write override but there is no matching base class function, or you write final and a further-derived class tries to implicitly or explicitly override the function anyway, you get a compile-time error.

Of the two, by far the more commonly useful is override; uses for final are rarer.

2. (a) What could be improved in the code’s correctness or style?

First, let’s consider some style issues, and one real error:

1. The code uses explicit new, delete, and an owning *.

Avoid using owning raw pointers and explicit new and delete except in rare cases like when you’re writing the internal implementation details of a low-level data structure.

{ base* pb = new derived; ... delete pb; }

Instead of new and base*, use make_unique and unique_ptr<base>.

{ auto pb = unique_ptr<base>{ make_unique<derived>() }; ... } // automatic delete here

Guideline: Don’t use explicit new, delete, and owning * pointers, except in rare cases encapsulated inside the implementation of a low-level data structure.

However, that delete brings us to another issue unrelated to how we allocate and manage the lifetime of the object, namely:

2. base’s destructor should be virtual or protected.

class base { public: virtual void f( int ); virtual void f( double ); virtual void g( int i = 10 ); };

This looks innocuous, but the writer of base forgot to make the destructor either virtual or protected. As it is, deleting via a pointer-to-base without a virtual destructor is evil, pure and simple, and corruption is the best thing you can hope for because the wrong destructor will get called, derived class members won’t be destroyed, and operator delete will be invoked with the wrong object size.

Guideline: Make base class destructors public and virtual, or protected and nonvirtual.

Exactly one of the following can be true for a polymorphic type:

Either destruction via a pointer to base is allowed, in which case the function has to be public and had better be virtual ;

and had better be ; or else it isn’t, in which case the function has to be protected ( private is not allowed because the derived destructor must be able to invoke the base destructor) and would naturally also be nonvirtual (when the derived destructor invokes the base destructor, it does so nonvirtually whether declared virtual or not).

Interlude

For the next few points, it’s important to differentiate three terms:

To overload a function f means to provide another function with the same name in the same scope but with different parameter types. When f is actually called, the compiler will try to pick the best match based on the actual parameters that are supplied.

a function means to provide another function with the same name in the same scope but with different parameter types. When is actually called, the compiler will try to pick the best match based on the actual parameters that are supplied. To override a virtual function f means to provide another function with the same name and the same parameter types in a derived class.

a virtual function means to provide another function with the same name and the same parameter types in a derived class. To hide a function f that exists in an enclosing scope (base class, outer class, or namespace) means to provide another function with the same name in an inner scope (derived class, nested class, or namespace), which will hide the same function name in an enclosing scope.

3. derived::f is neither an override nor an overload.

void derived::f( complex<double> )

derived does not overload the base::f functions, it hides them. This distinction is very important, because it means that base::f(int) and base::f(double) are not visible in the scope of derived.

If the author of derived intended to hide the base functions named f, then this is all right. Usually, however, the hiding is inadvertent and surprising, and the correct way to bring the names into the scope of derived is to write the using-declaration using base::f; inside derived.

Guideline: When providing a non-overridden function with the same name as an inherited function, be sure to bring the inherited functions into scope with a using-declaration if you don’t want to hide them.

4. derived::g overrides base::g but doesn’t say “override.”

void g( int i = 20 ) /* override */

This function overrides the base function, so it should say override explicitly. This documents the intent, and lets the compiler tell you if you’re trying to override something that’s not virtual or you got the signature wrong by mistake.

Guideline: Always write override when you intend to override a virtual function.

5. derived::g overrides base::g but changes the default argument.

void g( int i = 20 )

Changing the default argument is decidedly user-unfriendly. Unless you’re really out to confuse people, don’t change the default arguments of the inherited functions you override. Yes, this is legal C++, and yes, the result is well-defined; and no, don’t do it. Further below, we’ll see just how confusing this can be.

Guideline: Never change the default arguments of overridden inherited functions.

We could go one step further:

Guideline: Avoid default arguments on virtual functions in general.

Finally, public virtual functions are great when a class is acting as a pure abstract base class (ABC) that only specifies the virtual interface without implementations, like a C# or Java interface does.

Guideline: Prefer to have a class contain only public virtual functions, or no public virtual functions (other than the destructor which is special).

A pure abstract base class should have only public virtual functions. …

But when a class is both providing virtual functions and their implementations, consider the Non-Virtual Interface pattern (NVI) that makes the public interface and the virtual interface separate and distinct.

… For any other base class, prefer making public member functions non-virtual, and virtual member functions non-public; the former should have any default arguments and can be implemented in terms of the latter.

This cleanly separates the public interface from the derivation interface, lets each follow its natural form best suited for its distinct audience, and avoids having one function exist in tension from doing double duty with two responsibilities. Among other benefits, using NVI will often clarify your class’s design in important ways, including for example that the default arguments which matter to the caller therefore naturally belong on the public interface, not on the virtual interface. Following this pattern means that several classes of potential problems, including this one of virtuals with default arguments, just naturally don’t arise.

The C++ standard library follows NVI nearly universally, and other modern OO languages and environments have rediscovered this principle for their own library design guidelines, such as in the .NET Framework Design Guidelines.

2. (b) What did the programmer probably expect the program to print, but what is the actual result?

Now that we have those issues out of the way, let’s look at the mainline and see whether it does that the programmer intended:

int main() { base b; derived d; base* pb = new derived; b.f(1.0);

No problem. This first call invokes base::f( double ), as expected.

d.f(1.0);

This calls derived::f( complex<double> ). Why? Well, remember that derived doesn’t declare using base::f; to bring the base functions named f into scope, and so clearly base::f( int ) and base::f( double ) can’t be called. They are not present in the same scope as derived::f( complex<double> ) so as to participate in overloading.

The programmer may have expected this to call base::f( double ), but in this case there won’t even be a compile error because fortunately(?) complex<double> provides an implicit conversion from double, and so the compiler interprets this call to mean derived::f( complex<double>(1.0) ).

pb->f(1.0);

Interestingly, even though the base* pb is pointing to a derived object, this calls base::f( double ) because overload resolution is done on the static type (here base), not the dynamic type (here derived). You have a base pointer, you get the base interface.

For the same reason, the call pb->f(complex<double>(1.0)); would not compile, because there is no satisfactory function in the base interface.

b.g();

This prints 10, because it simply invokes base::g( int ) whose parameter defaults to the value 10. No sweat.

d.g();

This prints derived::g() 20, because it simply invokes derived::g( int ) whose parameter defaults to the value 20. Also no sweat.

pb->g();

This prints derived::g() 10.

“Wait a minute!” you might protest. “What’s going on here?” This result may temporarily lock your mental brakes and bring you to a screeching halt until you realize that what the compiler has done is quite proper. (Although, of course, the programmer of derived ought to be taken out into the back parking lot and yelled at.) The thing to remember is that, like overloads, default parameters are taken from the static type (here base) of the object, hence the default value of 10 is taken. However, the function happens to be virtual, and so the function actually called is based on the dynamic type (here derived) of the object. Again, this can be avoided by avoiding default arguments on virtual functions, such as by following NVI and avoiding public virtual functions entirely.

delete pb; }

Finally, as noted, this shouldn’t be needed because you should be using unique_ptrs which do the cleanup for you, and base should have a virtual destructor so that destruction via any pointer to base is correct.

Acknowledgments

Thanks in particular to the following for their feedback to improve this article: litb1, KrzaQ, mttpd.