In object-oriented programming, inheritance is the idea that a “derived” class can “inherit” the public methods and attributes of a “base” class (and also implement its own specific methods/attributes on top of that). For example, an ordered list could inherit everything from a linked list class, but redefine insert(). In Java we use the extends keyword.

The derived class is an instance of the base class. What this means is when we declare a Student object, we call the Person constructor then the Student constructor below the hood.

Inheritance allows us to implement large class hierarchies. Take, for instance, IO streams in C++:

The basics

Our motivation is that it’s pretty tedious to re-implement a class from scratch if it’s similar to another class. This also forces us to understand fully the specifics of the class we’re “copying” from. So we inherit methods/attributes instead. This allows us to reuse code (and all changes in the original class will be reflected in sub-classes), and doesn’t require us to understand the specifics of what happens under the hood.

We can also embed the base class in the derived class (as an attribute) to represent a “has a” relationship. Inheritance itself represents an “is a” relationship.

class Person {
	private:
		string name;
		int age;
	public:
		// constructors
		// getters/setters
		void print () {
			cout << "Name: " << name << endl;
		}
}
 
class Student: public Person {
	private:
		int id;
	public:
		Student () { ID = 0; }
		void setNameID(string n, int d) {
			Person::setName(n);
			id = d;
		}
		void print() { // method overriding
			cout << "ID: " << id << endl;
			Person::print();
		}
}

If members are private, we can’t access them from the derived class. We also don’t inherit the constructors of Person. If we re-define an inherited method, we override it. We can instead use the protected access modifier to denote otherwise private data except for derived classes.

We can also inherit from more than one base class in C++.

class TA: public GradStudent, public Employee {
	// some code
}

But Java can only inherit one class, i.e., it only allows for single inheritance.

Redefining or overloading

We never inherit constructors, copy constructors, operator=, and destructors — we must implement our own. This is the same thing we do with single classes that use dynamic memory allocation.

If we want to implement constructors that make use of base classes or methods that call base methods, we can:

class Circle {
	protected:
		int radius;
	public:
		Circle(int r) { radius = r; }
		void print() { cout << "Circle: " << radius << endl; }
};
 
class Wheel: public Circle {
	protected:
		int radius; // new variable, identical name
	public:
		Wheel(int rc, int rw): Circle(rc) {
			radius = rw;
		}
		void print() {
			Circle::print();
			cout << "Wheel: " << radius << endl;
		}
}
 
class Tire: public Wheel {
	protected:
		int radius; // yet another
	public:
		Tire(int rc, int rw, int rt): Wheel(rc, rw) {
			radius = rt;
		}
}

Constructors are invoked from the highest base class to derived classes. Destructors are invoked from the lowest-level up to the highest base-class (i.e., last-in, first-out/LIFO). Think about it like forging a chain link by link from a hook. You have to create the higher links before the lower ones, and to destroy it you have to destroy the lower links before the higher ones.

Mixing types

We also run into a problem when using operator=. Take the following example:

Polygon p;
Rectangle r;
p = r;
r = p; // ERROR
 
// see below
Polygon& operator=(Polygon& rhs) {
	// ...
}
r.operator=(p); // not rectangle

Because there’s mismatched types, we can’t use operator=. If rectangle is a derived class of polygon, then we can say that p=r, because rectangle is also a polygon class. But the other way around doesn’t work.

If , then intuitively it’s fine if: the types match (, ), or we say that a “rectangle” is a “polygon” (p = r). It’s not fine if we say that is a sub-class of (i.e., we can’t say that a polygon is a rectangle r = p).

Rectangle* r1;
Polygon* p1;
p1 = &r;
p1->set(3, 4); // calls set method in polygon
p1->area(); // error as area is a member of rectangle but not polygon
r1 = &p; // gives us an error

The r1 pointer can’t access all rectangle members as not all of them exist in the base class. Our problem is that we can’t access members of a derived class if the pointer to it is of the base class. The solution is using virtual functions, which facilitates dynamic dispatch.