In this code, the class List implements the simplest possible list with the three member functions Insert, Get, and Size as well as the constructor. The function ManipList is an example of some arbitrary function that uses of the List class, and it calls the insert function twice simpy as an example.
The TotalingList class inherits the List class and adds in a data member named total. This member holds the current total of all the numbers held in the list. The Insert function is overridden so that total is updated at each insertion.
The main function declares an instance of the TotalingList class. It inserts 10 and 5, and prints out the total. It then calls ManipList. It might surprise you that this actually compiles--if you look at the prototype for ManipList you can see that it expects a parameter of type List, not TotalingList. But C++ understands certain things about inherited classes, one of them being that a parameter of a base class type should accept any class derived from that base class as well. Therefore, since TotalingList is derived from the List class, ManipList will accept it. This is one of the features of C++ that makes inheritance so powerful--you can create derived classes and pass them to existing functions that know only about the base class.
When the code shown above runs however, it does not produce the correct result. It produces the output:
This output indicates that not only did the totaling not work, but the 100 and 200 were never inserted in the list during the call to ManipList. Part of this problem is occurring because of an outright error in the code--the parameter accepted by ManipList must be a pointer or a reference or no values are returned. Modifying the prototype for ManipList to the following partially fixes the problem:
Now the output looks like this:
It is educational to single-step through the ManipList and watch what happens. When the calls to the Insert functions occur, they route themselves to List::Insert rather than TotalingList::Insert.
This problem can also be solved however. It is possible in C++ to create a function with the prefix virtual, and this causes C++ to call the version of the function in the derived class. That is, when a function is declared as virtual, the compiler can call versions of the function that did not even exist when the code calling the function was written. To see this, add the word virtual in front of the Insert functions in both the List and TotalingList classes, as shown below:
Actually it is only necessary to place it in front of the function name in the base class, but its a good habit to perpetuate it in all derived classes as well to give some indication of what is happening.
Now when you execute the program, you will get the correct output:
What is happening? The word virtual in front of a function tells C++ that you plan to create new versions of this function in derived classes. That is, it lets you state future intentions for a class. When the virtual function is called, C++ looks at the class that called the function and picks the version of the function for that class, even if the derived class did not exist at the time that the function call was written.
What all of this means is that in many cases you have to think into the future when you are writing code. You have to think, "will I or anyone else ever need or want to change the behavior of this function?" If the answer is yes then the function should be declared as a virtual function.
You have to pay attention to several things in order for virtual functions to work correctly. For example, you have to actually predict the need for the function and remember to make it virtual in the base class. Another point can be seen in the program above--try removing the & from the parameter in the ManipList function and then single-step through the code. Even though the Insert function is tagged as virtual, the List::Insert function is called instead of the TotalingList::Insert function. The behavior changes because the parameter type List is acting like a type cast when the & is not there. Any class passed in is cast back to the base List class. With the & in place, this casting does not happen.
You see virtual functions everywhere in C++ class hierarchies. A typical hierarchy expects you to be changing behavior in the future to customize the library to your application. Virtual functions are also frequently used when the creator of the class cannot know what you will do with the class. For example, say that you are using a user interface class that implements buttons on the screen. When you create an instance of the button it paints itself onto the screen and behaves as a button should by highlighting itself when the button is clicked by the user. However, the person who wrote the class has no idea what people using the class plan to have the button do when it is clicked. In such cases, the author will create a virtual function named something like handleEvent that is called whenever the button is clicked. Then you override that virtual function with a function of your own that handles the button event properly.
The only way to fully understand this language is to write, and read, a lot of C++ code. You can learn a great deal by using and studying class libraries that other people have developed.
The many advantages of this language become apparent once it is fully understood. So start coding....