In the last section, we encountered an issue with makeIntroduction(const Person& p). When it called p.introduce(), it will always use Person::introduce(), even if p is actually a Student. We would like makeIntroduction to always use the most appropriate version of introduce. If we pass it a Student, we would hope that it would call Student::introduce() instead of the Person version.
This behavior, where the same code treats upcasted objects differently according to their actual type is known as polymorphism. The word Polymorphism literally means โmany formsโ (โpoly-โ for many, โmorphโ for form). In object-oriented programming, it refers to the idea of having a common interface (like introduce()) result in many different behaviors.
In C++, virtual functions are used to achieve polymorphism. They do so by modifying the code the compiler generates for the function. For virtual functions, the compiler generates code for the function call to examine the object at runtime in order to decide which function version to use.
Technically, this only needs to be added to the base class version. Doing so automatically makes the function virtual in any derived classes. But it can be helpful to also use virtual in the derived classes just to clearly indicate that the function is virtual.
This version of our program adds the virtual keyword to the two versions of the introduce() functions Other than that, nothing has been changed. However, after that simple change, the makeIntroduction function now behaves in a polymorphic fashion. Try it out and pay attention the the version of .introduce() used for Alex.
The main downside is a very slight performance hit. Code to look up the right object at runtime involves more steps that assumes we always will just run one version of the code. This performance difference is tiny. But a tiny cost paid thousands of times can add up. And if you are writing code that needs to be as fast as possible, you may not want to pay that cost.
One of the design goals of C++ is to let the programmer make decisions about when they care more about performance. So C++ requires us to say โyes please make this function virtualโ. Other programming languages (Python, Javascript, etc...) often make every function virtual and do not require the programmer to specify they want polymorphic behavior (or allow them to opt out of it).
So when should you use virtual? It is generally advisable to use virtual functions when you expect that your class will be subclassed and you want to allow derived classes to provide specific implementations of a function.
Here, .getName() is not virtual as we donโt expect subclasses to want to provide a โbetterโ version of how to get the name of a Person. And the constructor canโt be made virtual - Student will never provide a better way to make a plain Person (it will provide a way to make a Student that happens to be a Person).
However, we could make .getName() virtual. Try adding virtual before getName in Person. Nothing will happen other than the code will run an infinitesimal bit slower.
Better safe and slightly expensive than wrong and โfastโ (code isnโt really fast if it does the wrong thing). Feel free to add virtual to every function in a class that will be inherited. And if you donโt know if a class will be inherited from, assume it will be.
When you separate the declaration of a virtual function from its definition, you only add virtual to the declaration inside the class. The implementation outside can not have the virtual keyword.
class Person {
virtual void introduce() const; // In class declaration gets virtual
};
void Person::introduce() const {
// Outside of class definition of introduce does not get virtual
}
A little confusing. But donโt worry too much, if you by accident add virtual to the definition the compiler will produce an error to let you know. You just need to know that the message error: โvirtualโ outside class declaration is trying to tell you that you made that mistake.
class Student : public Person {
virtual void introduce() const override {...} // Example of using override
};
Doing so will not change how the code works and is not required. But it is a nice reminder of what is going on. It also tells the compiler to make sure that the function is actually overriding a base class function. This can prevent bugs such as declaring void intorduce() (spelling error!) in the child class and then wondering why it is never used. Doing so would make a new function in the child class instead of overriding a function from the parent class. With the override keyword, the compiler would mark inroduce() as an error because it does not successfully override any existing functions.