In Object Oriented Programming, Inheritance is the process by which objects of one class acquire the properties and functionality of objects of another class. It supports the concept of hierarchical classification. For example, the bird robin is a part of the class flying bird which is again a part of the class bird.
One reason to use inheritance is that it allows you to reuse code from a previous project but gives you the flexibility to slightly modify it if the old code doesn’t do exactly what you need for the new project. It doesn’t make sense to start every new project from scratch since some code will certainly be repeated in several programs and you should strive to build on what you did previously. Moreover, it is easy to make an error if we try to modify the original class, but we are less likely to make an error if we leave the original alone and only add to it. Another reason for using inheritance is if the project requires the use of several classes which are very similar but slightly different.
In this tutorial we will concentrate on the mechanism of inheritance and how to build it into a program. A better illustration of why you would use inheritance will be given in later tutorials where we will discuss some practical applications of object oriented programming.
Table of Contents
- Start with a simple C++ Class
- The Implementation of the Class
- Using the Vehicle Class
- Introducing Derived Classes
- Declaring a Derived Class
- Car Class Implementation
- Another Derived Class
- Truck Implementation
- Using all three Classes
- Our First Practical Inheritance
The principle of inheritance is available with several modern programming languages and is handled slightly differently with each. C++ allows you to inherit all or part of the members and methods of a class, modify some, and add new ones not available in the parent class. You have complete flexibility, and as usual, the method used with C++ has been selected to result in the most efficient code execution.
Start with a simple C++ Class
First, example the file named vehicle.h below for a simple class which we will use to begin our study of inheritance in this chapter. The header file uses #ifndef and #define directives to define the class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #ifndef VEHICLE_H #define VEHICLE_H class vehicle { protected: int wheels; float weight; public: void initialize(int in_wheels, float in_weight); int get_wheels(void); float get_weight(void); float wheel_loading(void); }; #endif |
This class header is straightforward and uncomplicated. It includes four methods designed for manipulating vehicle-related data. The specifics of each method are not crucial at this moment. While we’ll later identify it as a base or parent class, for now, treat it like any other class to highlight its similarity to previously studied classes. The explanation of the added keyword “protected” will follow shortly.
You can not compile or execute this file because it is only a header file
The Implementation of the Class
Examine the program named vehicle.cpp as below and you will find that it is the implementation of the vehicle class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #include "vehicle.h" // initialize to any data desired void vehicle::initialize(int in_wheels, float in_weight) { wheels = in_wheels; weight = in_weight; } // get the number of wheels of this vehicle int vehicle::get_wheels() { return wheels; } // return the weight of this vehicle float vehicle::get_weight() { return weight; } // return the weight on each wheel float vehicle::wheel_loading() { return weight/wheels; } |
The “initialize()” method assigns input values to the “wheels” and “weight” variables. Methods are in place to retrieve the number of wheels and weight, along with one performing a simple calculation to yield the load on each wheel. While more complex methods will be explored later, our current focus is on setting up class interfaces, keeping the implementations straightforward.
This class is uncomplicated and serves as a building block for the upcoming program. In subsequent tutorials, we’ll utilize it as a base class. Compile the class in preparation for the next example, but note that it lacks an entry point and cannot be executed at this stage.
Using the Vehicle Class
The program named transport.cpp below uses the vehicle class in exactly the same manner as we illustrated in the last chapter.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <iostream> #include "vehicle.h" int main(void) { vehicle car, motorcycle, truck, sedan; car.initialize(4, 3000.0); motorcycle.initialize(2, 900.0); truck.initialize(18, 45000.0); sedan.initialize(4, 3000.0); std::cout << "The car has " << car.get_wheels() << " wheels.\n"; std::cout << "The truck has a loading of " << truck.wheel_loading() << " pounds per wheel.\n"; std::cout << "The motorcycle weighs " << motorcycle.get_weight() << " pounds.\n"; std::cout << "The sedan weighs " << sedan.get_weight() << " pounds, and has " << sedan.get_wheels() << " wheels.\n"; return 0; } |
Output of the Program:
1 | The car has 4 wheels.<br>The truck has a loading of 2500 pounds per wheel.<br>The motorcycle weighs 900 pounds.<br>The sedan weighs 3000 pounds, and has 4 wheels. |
This signals that the vehicle class is essentially a standard C++ class. However, we’ll give it a unique touch by utilizing it unaltered as a base class in the upcoming example files to showcase inheritance. Inheritance involves enhancing an existing class to perform another, potentially more intricate task.
Understanding the inner workings of this program should be easy. It declares four vehicle class objects, initializes them, and outputs selected data points to demonstrate that the vehicle class functions effortlessly as a simple class—just as it is. For now, we’re keeping it simple and avoiding terms like “base class” or “derived class,” which we’ll delve into shortly.
Introducing Derived Classes
Take a look at the program named “car.h” for our first example of utilizing a derived class, also known as a child class.
1 2 3 4 5 6 7 8 9 10 11 12 | #ifndef CAR_H #define CAR_H #include "vehicle.h" class car : public vehicle { int passenger_load; public: void initialize(int in_wheels, float in_weight, int people = 4); int passengers(void); }; #endif |
The magic happens on line 4 with the addition of “: public vehicle,” which signifies that the “car” class inherits from the “vehicle” class. This derived class, “car,” encapsulates all the information from the base class, “vehicle,” and adds its unique details. Although we made no alterations to the “vehicle” class, it becomes a base class due to our utilization here. Notably, it can continue being used as a simple class in the earlier example program while also serving as a base class in a subsequent example program in this chapter. Its classification as a simple class or a base class depends on how it’s employed.
You can read more about Encapsulation in this tutorial: Encapsulation
In object-oriented programming, a class inheriting another is often called a derived class or a child class. The proper C++ term is “derived class“. Similarly, the formal C++ term for the inherited class is a base class, but you might encounter “parent class” or “superclass.”
A base class is quite general, covering a broad spectrum of objects. On the other hand, a derived class is somewhat more specific yet more practical. For instance, consider a base class named “programming language” and a derived class named “C++.” The base class can define Pascal, Ada, C++, or any programming language, providing a general perspective for each. Meanwhile, the derived class, “C++,” can specifically detail the use of classes but lacks the versatility to describe other languages. A base class tends to be more general, whereas a derived class is more specific.
In this scenario, the “vehicle” base class can declare objects representing trucks, cars, bicycles, or various other vehicles. Conversely, the “car” class can only declare objects of the car type due to limited applicable data. Hence, the “car” class is more restrictive and specific, while the “vehicle” class is broader and more general.
Declaring a Derived Class
Defining a derived class involves the header file for the base class, as seen in line 2. Then, following the name of the derived class in line 4, add a colon and the name of the base class. Objects declared as part of the “car” class inherit the two variables from the “vehicle” class and the unique variable declared in the “car” class named “passenger_load.”
An object of this class encompasses three of the four methods from the “vehicle” class and the two new ones declared here. Note that the “initialize()” method from the “vehicle” class is not available here, as it’s overridden by the local version in the “car” class. The local method takes precedence when the name is repeated, allowing customization of your new class.
It’s essential to reiterate that the implementation for the base class only needs to be supplied in its compiled form. Hiding the source code aids software developers for economic reasons and allows the practice of information hiding. The header for the base class must be available as a text file since the class definitions are necessary for usage.
Car Class Implementation
Examine the file named “car.cpp,” the implementation file for the “car” class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // car.cpp #include "car.h" void car::initialize(int in_wheels, float in_weight, int people) { passenger_load = people; wheels = in_wheels; weight = in_weight; } int car::passengers(void) { return passenger_load; } |
Notice that this file doesn’t explicitly indicate that it’s a derived class. Its status can only be determined by inspecting the header file for the class. Written in the same way as any other class implementation file, it hides its derived nature.
The implementations for the two new methods are crafted just like methods for any other class. If you believe you’ve grasped this file, go ahead and compile it for later use.
Another Derived Class
Check out the file named “truck.h” for another example of a class utilizing the “vehicle” class and adding to it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #ifndef TRUCK_H #define TRUCK_H #include "vehicle.h" class truck : public vehicle { int passenger_load; float payload; public: void init_truck(int how_many = 2, float max_load = 24000.0); float efficiency(void); int passengers(void); }; #endif |
It adds different elements because it specializes in things related to trucks, introducing two more variables and three methods. Again, ignore the “public” keyword following the colon for a few minutes.
A crucial point is that the “car” class and the “truck” class are independent; they are just derived classes of the same base class or parent class, as it’s sometimes called.
Note that both the “car” and the “truck” classes have methods named “passengers()”—this poses no problem and is perfectly acceptable. If classes are related, especially if they’re both derived classes of a common base class, it’s reasonable for them to perform somewhat similar tasks. In such cases, method names might be repeated in both child classes.
Truck Implementation
Examine the file named “truck.cpp” for the implementation of the “truck” class. It contains nothing unusual:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // truck.cpp #include "truck.h" void truck::init_truck(int how_many, float max_load) { passenger_load = how_many; payload = max_load; } float truck::efficiency(void) { return payload / (payload + weight); } int truck::passengers(void) { return passenger_load; } |
Using all three Classes
Explore the example program named “allvehicle.cpp” to see how all three classes discussed in this chapter can be utilized.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | // allvehic.cpp #include <iostream> #include "vehicle.h" #include "car.h" #include "truck.h" int main() { vehicle unicycle; unicycle.initialize(1, 12.5); std::cout << "The unicycle has " << unicycle.get_wheels() << " wheel.\n"; std::cout << "The unicycle's wheel loading is " << unicycle.wheel_loading() << " pounds on the single tire.\n"; std::cout << "The unicycle weighs " << unicycle.get_weight() << " pounds.\n\n"; car sedan; sedan.initialize(4, 3500.0, 5); std::cout << "The sedan carries " << sedan.passengers() << " passengers.\n"; std::cout << "The sedan weighs " << sedan.get_weight() << " pounds.\n"; std::cout << "The sedan's wheel loading is " << sedan.wheel_loading() << " pounds per tire.\n\n"; truck semi; semi.initialize(18, 12500.0); semi.init_truck(1, 33675.0); std::cout << "The semi weighs " << semi.get_weight() << " pounds.\n"; std::cout << "The semi's efficiency is " << 100.0 * semi.efficiency() << " percent.\n"; return 0; } |
This program utilizes the parent class “vehicle” to declare objects and also leverages the two child classes to declare objects. The inclusion of all three class header files in lines 3 through 5 allows the program to access the components of these classes. Note that the actual implementations of the classes are not visible here, demonstrating the ability to use the code without exposing its source. However, the header file definition must be accessible.
In this example program, only one object of each class is declared and used, but you can declare and use as many as needed for your programming task. The source code remains clean and uncluttered since the classes were developed, debugged, and stored away beforehand, with simple interfaces. The program’s operation should be straightforward if you are familiar with the concepts discussed.
The three classes and the main program can be compiled in any desired order before linking the resulting object files together. Ensure you follow the necessary steps to compile and execute this program, as effective C++ usage often involves compiling and linking multiple files.
Our First Practical Inheritance
We have a date class as defined by date.h program below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // This class represents a date with day, month, and year. #ifndef DATE_H #define DATE_H class date { protected: int day; int month; int year; public: // Constructor date(int in_month, int in_day, int in_year) : month(in_month), day(in_day), year(in_year) {} // Methods int get_day() const { return day; } int get_month() const { return month; } int get_year() const { return year; } void set_date(int in_month, int in_day, int in_year) { month = in_month; day = in_day; year = in_year; } }; #endif |
Now we are introducing a new member variable, day_of_year, and a corresponding method. While there might be more efficient ways to add day_of_year, our primary focus is to provide a tangible illustration of inheritance rather than crafting a flawless class. The new_date class, therefore, features the addition of one variable and one method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // This class inherits the date class and adds one variable and one // method to it. #ifndef NEWDATE_H #define NEWDATE_H #include "date.h" class new_date : public date { protected: int day_of_year; // New member variable public: int get_day_of_year(void); // New method }; #endif |
The accompanying program, newdate.cpp, implements the added method, leveraging the array “days[]” from the date class implementation. The logic in the “get_day_of_year()” method is straightforward, adjusting for leap years while keeping simplicity in mind.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include "newdate.h" extern int days[]; // This routine ignores leap year for simplicity, and adds // the days in each month for all months less than the // current month, then adds the days in the current month // up to today. int new_date::get_day_of_year(void) { int index = 0; day_of_year = 0; while (index < month) day_of_year += days[index++]; return (day_of_year += day); } |
Now in the following C++ program we demonstrate the new class in a simple manner to emphasize that the derived class is just as user-friendly as the base class. The main program remains unaware that it is working with a derived class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <iostream> #include "newdate.h" int main(void) { new_date now, later, birthday; later.set_date(12, 31, 1991); birthday.set_date(2, 19, 1991); std::cout << "Today is day " << now.get_day_of_year() << "\n"; std::cout << "Dec 31 is day " << later.get_day_of_year() << "\n"; std::cout << "Feb 19 is day " << birthday.get_day_of_year() << "\n"; return 0; } |
Compile and link this program to gain hands-on experience. Remember to link the object code for the original date class, the newdate class, and the main program.