Search

12.2 — Virtual functions and polymorphism

In the previous lesson on pointers and references to the base class of derived objects, we took a look at a number of examples where using pointers or references to a base class had the potential to simplify code. However, in every case, we ran up against the problem that the base pointer or reference was only able to call the base version of a function, not a derived version.

Here’s a simple example of this behavior:

This example prints the result:

rBase is a Base

Because rBase is a Base reference, it calls Base::getName(), even though it’s actually referencing the Base portion of a Derived object.

In this lesson, we will show how to address this issue using virtual functions.

Virtual functions and polymorphism

A virtual function is a special type of function that, when called, resolves to the most-derived version of the function that exists between the base and derived class. This capability is known as polymorphism. A derived function is considered a match if it has the same signature (name, parameter types, and whether it is const) and return type as the base version of the function. Such functions are called overrides.

To make a function virtual, simply place the “virtual” keyword before the function declaration.

Here’s the above example with a virtual function:

This example prints the result:

rBase is a Derived

Because rBase is a reference to the Base portion of a Derived object, when rBase.getName() is evaluated, it would normally resolve to Base::getName(). However, Base::getName() is virtual, which tells the program to go look and see if there are any more-derived versions of the function available between Base and Derived. In this case, it will resolve to Derived::getName()!

Let’s take a look at a slightly more complex example:

What do you think this program will output?

Let’s look at how this works. First, we instantiate a C class object. rBase is an A reference, which we set to reference the A portion of the C object. Finally, we call rBase.getName(). rBase.GetName() evaluates to A::getName(). However, A::getName() is virtual, so the compiler will call the most-derived match between A and C. In this case, that is C::getName(). Note that it will not call D::getName(), because our original object was a C, not a D, so only functions between A and C are considered.

As a result, our program outputs:

rBase is a C

A more complex example

Let’s take another look at the Animal example we were working with in the previous lesson. Here’s the original class, along with some test code:

This prints:

Fred says ???
Garbo says ???

Here’s the equivalent class with the speak() function made virtual:

This program produces the result:

Fred says Meow
Garbo says Woof

It works!

When animal.speak() is evaluated, the program notes that Animal::speak() is a virtual function. In the case where animal is referencing the Animal portion of a Cat object, the program looks at all the classes between Animal and Cat to see if it can find a more derived function. In that case, it finds Cat::speak(). In the case where animal references the Animal portion of a Dog object, the program resolves the function call to Dog::speak().

Note that we didn’t make Animal::GetName() virtual. This is because GetName() is never overridden in any of the derived classes, therefore there is no need.

Similarly, the following array example now works as expected:

Which produces the result:

Fred says Meow
Garbo says Woof
Misty says Meow
Pooky says Woof
Truffle says Woof
Zeke says Meow

Even though these two examples only use Cat and Dog, any other classes we derive from Animal would also work with our report() function and animal array without further modification! This is perhaps the biggest benefit of virtual functions -- the ability to structure your code in such a way that newly derived classes will automatically work with the old code without modification!

A word of warning: the signature of the derived class function must exactly match the signature of the base class virtual function in order for the derived class function to be used. If the derived class function has different parameter types, the program will likely still compile fine, but the virtual function will not resolve as intended.

Use of the virtual keyword

Only the most base class function needs to be tagged as virtual for all of the derived functions to work virtually. However, having the keyword virtual on the derived functions does not hurt, and it serves as a useful reminder that the function is a virtual function rather than a normal one. Consequently, it’s generally a good idea to use the virtual keyword for virtualized functions in derived classes even though it’s not strictly necessary.

Return types of virtual functions

Under normal circumstances, the return type of a virtual function and its override must match. Consider the following example:

In this case, Derived::getValue() is not considered a matching override for Base::getValue() (it is considered a completely separate function).

Do not call virtual functions from constructors or destructors

Here’s another gotcha that often catches unsuspecting new programmers. You should not call virtual functions from constructors or destructors. Why?

Remember that when a Derived class is created, the Base portion is constructed first. If you were to call a virtual function from the Base constructor, and Derived portion of the class hadn’t even been created yet, it would be unable to call the Derived version of the function because there’s no Derived object for the Derived function to work on. In C++, it will call the Base version instead.

A similar issue exists for destructors. If you call a virtual function in a Base class destructor, it will always resolve to the Base class version of the function, because the Derived portion of the class will already have been destroyed.

Rule: Never call virtual functions from constructors or destructors

The downside of virtual functions

Since most of the time you’ll want your functions to be virtual, why not just make all functions virtual? The answer is because it’s inefficient -- resolving a virtual function call takes longer than resolving a regular one. Furthermore, the compiler also has to allocate an extra pointer for each class object that has one or more virtual functions. We’ll talk about this more in future lessons in this chapter.

Quiz time

1) What do the following programs print? This exercise is meant to be done by inspection, not by compiling the examples with your compiler.

1a)

Show Solution

1b)

Show Solution

1c)

Show Solution

1d)

Show Solution

1e)

Show Solution

1f)

Show Solution

12.2a -- The override and final specifiers, and covariant return types
Index
12.1 -- Pointers and references to the base class of derived objects

71 comments to 12.2 — Virtual functions and polymorphism

  • Val

    Because rBase is a Base reference, it calls Base::getName(), even though it’s actually referencing the Base portion of a Derived object. <- Looks like wrong statement.

    Because rBase is a Base reference, it calls Base::getName(), even though it’s actually referencing the Derived portion of a Derived object. <- That’s correct?

    • Alex

      No, it’s correct as written. The Base reference is referencing the Base portion of a Derived object. The Base reference doesn’t even know there’s a Derived portion of the Derived object.

  • Meysam

    The answer to Question 1e) is not correct.  B::getName() and c::getName() are const. Maybe there is a typo and const should be removed for B::getName() and c::getName(). Thanks!

    • Alex

      No, b::GetName() and c::getName() return a const char* value, they aren’t const themselves. If a member function is const, the const comes after the function name.

  • David

    Why do virtual functions work for pointers and references but not copies?

    • Alex

      Because pointers and references can point to the base portion of an inherited object. If you try to copy the base class of an inherited object, you get object slicing, where the base class is copied, but the derived class(es) are not, leading to a situation where virtual functions don’t work because the derived portion of the class has been lost. I cover object slicing later in the chapter.

  • Carlos

    Hi Alex

    I do not understand why virtual is useful.

    I mean. If you have a base variable, the main interest is not to call the getName function inside base instead of derived?

    For me does not make any sense.

    • Alex

      It allows us to write a single report() function that can take any kind of Animal, rather than having to write separate versions for each Class.

      Later on, we’ll also see how it allows us to create Arrays that can hold any kind of Animal.

Leave a Comment

Put C++ code inside [code][/code] tags to use the syntax highlighter