Early & Late Binding
What would happen if you were to remove the keyword virtual from the definition of the toString() member function in the Person class? Your code would still compile, but the toString() function would no longer be overridden; it would be hidden in the derived class Student.
Functions are bound to an object depending on how they are declared. A non-virtual function is bound at compile time to the class that it is defined in. A non-virtual function defined in the Person class (such as getName()), will always be bound to the Person class, and cannot be overridden in any subsequent classes.
This is called early binding (or compile-time binding).
When you add the keyword virtual to a function, the function call binding is not determined at compile time, but when the program is run. Instead of looking at the type of the pointer or reference used in the function call, the actual object pointed to is used to decide which function to call.
This decision is made when your program runs. If your Shape* points to a Circle object, then Circle::draw() will be called, but only if draw() is a virtual function.
This is called late-binding or dynamic dispatch. In Java, all methods use late binding, but in C++ you, as the base-class designer get to decide which version to use, through the application of the keyword virtual.
How Late Binding Works
Virtual member functions are implemented by adding a new pointer to every object that contains at least one virtual function. This pointer is called a vptr and it points to a table of functions, called a vtable or Virtual Method Table. The vtable contains the actual addresses of the functions to be called for that class.
Using this illustration, let's see how late binding, or dynamic dispatch works:
- You call emp[0]->raiseSalary()
- Your call is routed though the vptr in emp[0], which is actually a Manger, and your call eventually finds the address of the Manager::raiseSalary() function inside the Manager vtable.
- You call emp[1]->promote()
- Your call is routed through the vptr in emp[1], which is actually an Engineer. This vptr points to the Engineer vtable where it finds the Engineer::promote() function.
Multiple Inheritance
C++ includes a capability known as multiple inheritance, which allows a class to be derived from two (or more) base classes. Multiple inheritance lets you create a class AmphibiousVehicle from the parents LandVehicle and WaterVehicle.
An AmphibiousVehicle inherits wheels or treads from its LandVehicle parent, and a prop or screw and rudder from its WaterVehicle parent. You can't do this in Java, because Java classes can have only one parent class.
The standard stream libraries include classes that are both input and output streams. The fstream class at the bottom is an iostream, which is in turn—if you follow the arrow leading up and to the left—an istream.
The fstream class therefore inherits all the member functions that pertain to istream objects. If you instead follow the arrow up and to the right, you discover that the fstream class is an ostream, which means that it inherits these member functions as well.
Contraction
Suppose you have a class which simulates a Cadillac. It has an exceptionally fine sound system, which required a lot of effort to implement and of which you're especially proud.
Now you want to reuse that sound system in a portable GiPod class: Because you’ve already created the Cadillac class, why not just create a derived class, and then eliminate all the member functions that have nothing to do with playing music, transforming the car into a mere sound system?
To reuse the code you’ve already written, you replace brake(), accelerate(), and all the other "extra" methods from the Cadillac class with empty braces. In traditional computer-science terms, you replace them with a NOP (No OPeration).
This practice, called contraction, is a trap! You should avoid doing this for two reasons:
- You're violating the substitutability rule. You will undoubtedly break some code that relied on all Cadillac objects carrying out certain operations.
- It’s more work than doing the right thing!
Let's look at how you can use private inheritance and composition to do this correctly.
Private Inheritance
Private inheritance is one way to solve this problem. Private inheritance means you want to inherit the implementation of a class, not the interface. Use this when a class has some functionality that you want to exploit, but you don't want to use the interface of the base class.
To use private inheritance,replace the keyword public with private, or, simply omit it altogether. I'd recommend that you explictly specify privateto prevent subsequent maintenance programmers from "fixing" your code inadvertently.
class GiPod : private Cadillac
{
// ...
};
GiPod objects are not, in the is-a sense, Cadillac. Calling any "inherited" member functions will fail. To call any of the inherited member functions, you must add those member functions to the new interface, with a using declaration like this:
class GiPod : private Cadillac
{
public:
using Cadillac::playMusic;
};
When you "import" a member function like this, you don't need to specify the arguments or supply an argument list. You do need the public: if you want the name moved into the public section.
Composition
Instead of using private inheritance, a better solution is to use composition. Composition creates a new class by combining simpler classes, using instances of the simpler class as the data members. In composition, an object is composed of other objects, which make up its "parts." That's why it's called (informally) a has-a relationship; because we can say that:
- A car has a motor, or
- A bicycle has a seat, or
- A computer has a CPU
Here is some code for a version of the GiPod which uses composition; note that it needs to explicitly write the prototypes for any member functions that it uses.
class GiPod
{
public:
void playMusic() const
{
caddy.playMusic(); // forward or delegate
}
private:
Cadillac caddy; // data member
};