
Inheritance
Inheritance is a programming concept that allows us to reuse classes. Programming languages provide different implementations of inheritance, but the general rule always stands: the class relationship should answer the is-a question. For example, a Car is-a Vehicle, which allows us to inherit the Car from the Vehicle:
class Vehicle {
public:
void move();
};
class Car : public Vehicle {
public:
Car();
// ...
};
The Car now has the move() member function derived from the Vehicle. Inheritance itself represents a generalization/specialization relationship, where the parent class (Vehicle) is the generalization and the child class (Car) is the specialization.
You should only consider using inheritance if it is absolutely necessary. As we mentioned earlier, classes should satisfy the is-a relationship, and sometimes, this is a bit tricky. Consider the Square and Rectangle classes. The following code declares the Rectangle class in its simplest possible form:
class Rectangle {
public:
// argument checks omitted for brevity
void set_width(int w) { width_ = w; }
void set_height(int h) { height_ = h; }
int area() const { return width_ * height_; }
private:
int width_;
int height_;
};
The Square is-a Rectangle, so we could easily inherit it from the Rectangle:
class Square : public Rectangle {
public:
void set_side(int side) {
set_width(side);
set_height(side);
}
int area() {
area_ = Rectangle::area();
return area_;
}
private:
int area_;
};
The Square extends the Rectangle by adding a new data member, area_, and overwriting the area() member function with its own implementation. In practice, the area_ and the way we calculate its value are redundant; we did this to demonstrate a bad class design and to make the Square extend its parent to some extent. Soon, we will conclude that the inheritance, in this case, is a bad design choice. Square is a Rectangle, so it should be used as a Rectangle wherever the Rectangle is used, as shown here:
void make_big_rectangle(Rectangle& ref) {
ref->set_width(870);
ref->set_height(940);
}
int main() {
Rectangle rect;
make_big_rectangle(rect);
Square sq;
// Square is a Rectangle
make_big_rectangle(sq);
}
The make_big_rectangle() function takes a reference to the Rectangle and the Square inherits it, so it's totally fine to send a Square object to the make_big_rectangle() function; the Square is-a a Rectangle. This example of the successful substitution of a type with its subtype is known as the Liskov Substitution Principle. Let's find out why this substitution works in practice and then decide if we made a design mistake by inheriting the Square from the Rectangle (yes, we did).