Declaring that a class is another class type is only part of the reason to use inheritance. It’s actually much more powerful than just allowing you to refer to a group of different class instances all by some common base type. When you create a new class that derives from a base class, you can actually change the behavior of the base class methods to adapt them to the specific needs of the new derived class.
You start this process in a base class by declaring a method to be virtual. Just use the keyword virtual before the method declaration in the class.
A very important point that you must remember is that if you intend a class to serve as a base class, then make sure to declare the destructor to be virtual. The end of this episode explains how your classes may not be properly destructed if you forget this.
This episode describes a specific example of a class called bee and another class called bumbleBee. Both classes declare a method called sting and they each have slightly different behavior. It’s this difference in behavior that you should be on the lookout for when deciding if you need to inherit from another class.
Listen to the full episode or read the full transcript below.
This is called overriding a method. The process starts in the base class. Whenever you’re designing a class, you should ask yourself if your class will ever need to serve as a base class for some other class. If so, then you can facilitate this by thinking about which of your class methods some other derived class might want to change. You then declare these methods to be virtual.
One very important point is that in order for a class to work properly as a base class, you must declare your class destructor to be virtual. Even if you have no other methods that you think a derived class might want to override, make sure that your destructor is virtual. If you don’t do this, then it’s a huge sign that maybe your class should not be derived from at all.
So make sure that you declare at least a virtual destructor if you think there’s any chance that some developer will ever want to create a class that derives from your class.
You declare a method to be virtual by putting the keyword virtual in front of the method declaration in your class declaration in the header file. At least for C++ that has header files. C# also uses the virtual keyword just without a separate .cpp file for the method implementations. So in C++, if you then implement the virtual method in a .cpp file that’s outside of your class declaration, then you don’t use the virtual keyword there. The virtual keyword goes only in the class declaration.
Declaring any method in your class to be virtual makes your instances slightly larger in memory. But you only pay for this with the first virtual method. Adding more virtual methods will not add any extra memory requirements.
There’s a little more work that needs to be done to call a virtual method. It basically means that whenever you call a virtual method, you end up going through another layer of code that’s really just a pointer to the actual method that will be called. This extra layer is called a vtable which stands for virtual table. The table is just a series of pointers that point to the most derived method for your class.
What does this mean? Let’s start out with a class that doesn’t inherit from any other class. This will be our base class for this example. Let’s give it the witty name bee. Most programming examples use a boring letter B but we’re going to use bee as in the insect that flies and makes honey. Our bee class will have a method called sting and we’ll declare this method to be virtual so that other classes can change it if needed. And let’s not forget to make the destructor virtual. When we implement the sting method for the bee class, it will eventually cause the instance of the bee to die because in real life, bees lose their stingers and die.
Then we declare a new class with another witty name of bumbleBee and make the bumbleBee class inherit publicly from the bee class. We declare the destructor to be virtual and declare a sting method here too that looks just like the sting method in the bee class. Now, some languages may not require that you declare the new sting method to be virtual or even that you declare the destructor in the bumbleBee class to be virtual. The fact that the methods were originally declared virtual is enough for some languages. I say, if at all possible, go ahead and declare the destructor and the sting methods in the bumbleBee class to be virtual as well. This will only help you later when you’re looking at your code to be abel to remember which methods are virtual and which aren’t.
The sting method in the bumbleBee class has a different behavior that does not result in the death of the insect. In fact, the bumbleBee class can even use the sting method again and again.
This is the type of behavior difference that you want to look for when designing your classes. There may be other differences between bees and bumblebees that I don’t know about.
In earlier episodes, I mentioned that you don’t normally need a lot of math in order to program. You really just need to be able to spot behavior differences such as this example in order to design good class hierarchies. Now, if your classes are related to math itself, then that’s a different story.
You might wonder what’s the big deal about overriding methods like this. If you have an instance of the bee class and call sting then the bee dies. And if you have an instance of the bumbleBee class and call sting, of course you’re going to call the bumbleBee version of sting that will keep your bumblebee alive to sting another day. Why do you need virtual methods and overloading for this?
There’s a couple big reasons. The first is that you get the correct method based on the actual type of the instance and not on how you are referring to the instance. In other words, you might have many instances of the bee class or at least you think they’re all bees. But some of them might actually be bumblebees. This is because you can always refer to a derived class instance as if it was just an instance of a base class. In other words, because we declared that a bumbleBee IS A bee, then we can refer to the bumbleBee as if it was a bee. It doesn’t work the other way because not all bees are bumblebees. Even though you have this collection of bees, the running program will know which ones are actually bumbleBees and which ones are just bees. You just call the sting method and can rely on the correct version being called.
I should say that this example doesn’t work so well with a collection of bee instances directly. Because the collection class, and yes, the collection is a class too, the collection class might try allocating fixed amounts of memory for the contents. If your bee class and bumbleBee class require different amounts of memory, then this is just asking for trouble. What ends up happening is probably not what you’d expect. You end up with just bees in your collection and anything that used to make up the bumbleBee part gets cut off.
The way you normally work with this is through pointers. You declare that your collection will hold pointers to bees instead of bees directly. Because all pointers are the same size regardless of what they actually point to, then it doesn’t matter anymore if some of the instances are more than just bees. When you call the sting method through the pointer to any particular bee instance, then the correct sting method will be called based on the actual type that the pointer points to.
This concept of being able to call a virtual method on what you think is just a base class and have it actually call the correct method based on the real instance type is called polymorphism. Most of the time you’ll know that some of the items in the collection will be more than just bees. You’ll know this because your design relies on polymorphism.
Have you ever seen one of those videos where a picture slowly morphs into something different? Maybe a picture of one person changes to look like somebody else. This is called morphing. Polymorphism in programming is similar. You call a method through a pointer to a base class and some other method gets called instead that matches the actual type of the instance.
Here’s another big reason for why virtual methods and overloading is so useful. Let me ask you this. Do you think that you have to always call a virtual method in order to trigger polymorphism? Not at all. Let’s give our bee class another method called attack and this time, the method will not be virtual. The new method doesn’t need to be virtual because let’s say that our research into bee psychology shows that all bees, wasps, hornets, bumblebees, etc. all go through the same steps when attacking. Now I have no idea if this is correct or not. This is just an example. I’m not really a bee expert. This new attack method performs a series of actions and the actions themselves may result in slightly different behavior. That’s where the virtual methods come into play. At some point in the attack method, it will call sting. But which sting will it call? It can’t call a bumbleBee sting because the bee class really doesn’t know anything about the bumbleBee class. Remember, bees are not bumbleBees.
The attack method just does the only thing it knows how to do and calls its own sting method. But because the sting method is virtual, your application will end up calling the correct version. The attack method is just a normal method in the bee class that knows nothing about any other types of bees and yet can end up calling the right method because the instance of the object just happens to be a bumbleBee that inherits from a bee.
This gives you, the programmer, a whole new way to design your software. You can factor common behavior into base classes and then through virtual methods, you can orchestrate activities that change based on the actual types involved.
And the really cool thing is that this adapts as you introduce new classes. You might start out with just bees and bumbleBees. Then when you add a new class for hornets, the new class just merges right into your design. All of a sudden, your simple method in the bee class that thinks it’s only calling its own sting method can now start calling the version of sting for hornets whenever the actual type happens to be a hornet.
At the beginning of this episode, I warned that base classes should always have virtual destructors. You should be able to understand why now. Let’s say we have our collection of bee pointers which is really a mix of some bees, some bumbleBees and some hornets. And we’re done with this collection and need to destroy all the objects and free up all the memory used. Remember that all we know about the instances is that they’re all bee pointers. When we delete the pointers, which destructor will be called? If the destructors are not virtual, then we’ll go through the entire collection calling only bee destructors. The memory of the bee or the bumbleBee or the hornet itself will be reclaimed but if there was anything special that also needed to be deleted or cleaned up that existed only in the bumbleBee part, or in the hornet part, then that will be missed. But if the destructors are virtual, then we’ll be able to clean up everything properly.