Class Inheritance

One of the foundations of object-oriented programming is inheritance. Often, two objects are related and we can save coding by reusing code from one object in another. Rather than retyping or cutting and pasting, we can simply pass down the members of one object to another.
The lower-level link is called the base class (or parent) and the subsequent one is called the derived class (or child).

Inheritance should reflect an IS-A relationship between the two objects. A cat is-a animal. A rectangle is-a shape.

Member Inheritance

Members declared in the base class cannot be inherited if they are declared private. They must be public or protected to be transmitted.

The syntax to declare a derived class is

class child-class: access-mode parent;

The access category is one of private, public, or protected. If not specified the usual default of private is assumed.

C++ permits multiple parents, but this is generally discouraged.

class child-class: access-mode parent1, access-mode parent2;

The access-mode specifier for the base class affects how the members of that class are transmitted to the derived class. If it is public the public and protected members of the base class retain that access mode in the derived class. If it is protected, both public and protected members are transmitted as protected. In the private case, all members are transmitted as private. In all cases, private members in the base class are not accessible directly to the derived class and an accessor must be provided if they should be available. Declaring the base class public is the most common due to the restrictions imposed by the other access modes.

Example

#include <iostream>
#include <string>
using namespace std;

class Animal {
   protected:
      string name;
      string food;
      string vocalization;
      float foodQuantity;
   public:
       void Iam(string name, string vocalization);
       void feedme(string food, float foodQuantity);
       void printme(); 
};

void Animal::Iam(string name, string vocalization) {
    this->name=name;
    this->vocalization=vocalization;
}

void Animal::feedme(string food, float foodQuantity) {
    this->food=food;
    this->foodQuantity=foodQuantity;
}

void Animal::printme() {
    cout<<"I am "<<name<<" I eat "<<food<<" at "<<foodQuantity<<" per day.\n";
}

class Antelope : public Animal {
   public:
	string species;
	string getSpecies();
};

string Antelope::getSpecies() {
    return species;
}

class Reptile : public Animal {
   public:
	string order;
	string getOrder();
};

string Reptile::getOrder() {
    return order;
}

int main() {

    Antelope jumper;
    jumper.Iam("Jenny","urk");
    jumper.feedme("hay",12.0);
    jumper.species="springbok";
    cout<<"I'm a "<<jumper.getSpecies()<<" ",jumper.printme();

    Reptile lizard;
    lizard.Iam("Jimmy","silent");
    lizard.feedme("bugs",0.5);
    lizard.order="lepidosaur";
    cout<<"I'm a "<<lizard.getOrder()<<" ",lizard.printme();

    return 0;
}

Notice that an antelope is-a animal and a reptile is-a animal, but an antelope is not a reptile. So derived classes do not need to have a direct relationship with one another aside from their inheritance.

We could introduce another level of derivations since “Reptile” is a broader category than “Antelope.” For example, we could declare class Mammal

class Mammal: public Animal {
   protected:
      string furColor;
      string order;
      string getOrder();
};

We could then derive Antelope from Mammal. Further subdivisions would be possible (class Ungulate and so forth). Derived classes can continue like this in an inheritance chain indefinitely, though it would be poor practice to set up more than a few links in the chain.

graph TD; A(Animal) --> B(Reptile) A(Animal) --> C(Mammal) C(Mammal) --> D(Antelope)

Constructors

Constructors, destructors, and friend classes are not inherited from the base class by derived classes. We can still create constructors, and the derived class can invoke the parent constructor.

#include <iostream>
#include <string>
using namespace std;

class Parent {
    protected:
        int myID;
        string name;

    public:
        Parent(string name, int myID);
        string getName();
        int getID();
};

Parent::Parent(string name, int myID) {
    this->name=name;
    this->myID=myID;
}

string Parent::getName() { 
    return name; 
}

int Parent::getID() { 
    return myID; 
}

class Child: public Parent {
    private:
        int age;
    public:
        Child(string name, int myID, int age);
        int getAge();
};

Child::Child(string name, int myID, int age) : Parent(name, myID) {
    this->age=age;
}

int Child::getAge() { 
    return age; 
}

int main() {
    Child billy("Bill",345,20);
    cout<<billy.getName()<<" "<<billy.getID()<<" "<<billy.getAge()<<"\n";
    return 0;

}

In this example, the child inherits the getName accessor from the parent. But age does not refer back to the parent, since that variable occurs only in the child, so it must be explicitly declared. The constructor for the Child class invokes the Parent constructor and then sets the age.

In C++11 the using keyword may be employed to bring in the base constructor.

class Child: public Parent {
    private:
        int age;
    public:
        using Parent::Parent;
        void setAge();
        int getAge();
};

The Parent constructor cannot be used to set the new member age, so a mutator would be defined. For this reason, some software engineers recommend keeping the older parent constructor syntax if the derived class defines its own constructor.

Exercises

  1. Add a new class Mammal as sketched above. Derive Antelope from that. Add an attribute scaleColor to the Reptile class.
Example solution

#include <iostream>
#include <string>
using namespace std;

class Animal {
   protected:
      string name;
      string food;
      string vocalization;
      float foodQuantity;
   public:
       void Iam(string name, string vocalization);
       void feedme(string food, float foodQuantity);
       void printme(); 
};

void Animal::Iam(string name, string vocalization) {
    this->name=name;
    this->vocalization=vocalization;
}

void Animal::feedme(string food, float foodQuantity) {
    this->food=food;
    this->foodQuantity=foodQuantity;
}

void Animal::printme() {
    cout<<"I am "<<name<<" I eat "<<food<<" at "<<foodQuantity<<" per day.\n";
}

class Reptile : public Animal {
   public:
	string scaleColor;
	string order;
	string getOrder();
};

string Reptile::getOrder() {
    return order;
}

class Mammal : public Animal {
   public:
	string furColor;
	string order;
	string getOrder();
};

string Mammal::getOrder() {
    return order;
}

class Antelope : public Mammal {
   public:
	string species;
	string getSpecies();
};

string Antelope::getSpecies() {
    return species;
}

int main() {

    Antelope jumper;
    jumper.Iam("Jenny","urk");
    jumper.feedme("hay",12.0);
    jumper.order="artiodactyla";
    jumper.furColor="tan";
    jumper.species="springbok";
    cout<<"I'm a "<<jumper.getSpecies()<<" ",jumper.printme();
    cout<<"My fur is "<<jumper.furColor<<".\n";

    Reptile lizard;
    lizard.Iam("Jimmy","silent");
    lizard.feedme("bugs",0.5);
    lizard.order="lepidosaur";
    cout<<"I'm a "<<lizard.getOrder()<<" ",lizard.printme();

    return 0;
}

  1. Create a constructor for Animal that sets the name, food, foodQuantity, and vocalization. Pass it through to the descendant classes and in each one, add the attributes new to that class. Remove functions made unnecessary by the constructor. Optionally implement the using syntax in the Antelope class. Depending on your compiler version, you may need to add a flag -std=c++11 or equivalent.
Example solution

#include <iostream>
#include <string>
using namespace std;

class Animal {
   protected:
      string name;
      string food;
      string vocalization;
      float foodQuantity;
   public:
       Animal(string name, string vocalization, string food, float foodQuantity);
       void printme(); 
};

Animal::Animal(string name, string vocalization, string food, float foodQuantity) {
    this->name=name;
    this->vocalization=vocalization;
    this->food=food;
    this->foodQuantity=foodQuantity;
}

void Animal::printme() {
    cout<<"I am "<<name<<" I eat "<<food<<" at "<<foodQuantity<<" per day.\n";
}

class Reptile : public Animal {
   public:
	string scaleColor;
	string order;
	string getOrder();
	Reptile(string name, string vocalization, string food, float foodQuantity, string scaleColor, string order);
};

Reptile::Reptile(string name, string vocalization, string food, float foodQuantity, string scaleColor, string order) : Animal(name,vocalization,food,foodQuantity) {
    this->name=name;
    this->vocalization=vocalization;
    this->food=food;
    this->foodQuantity=foodQuantity;
    this->order=order;
    this->scaleColor=scaleColor;
}

string Reptile::getOrder() {
    return order;
}

class Mammal : public Animal {
   public:
	string furColor;
	string order;
	string getOrder();
	Mammal(string name, string vocalization, string food, float foodQuantity, string furColor, string order);
};

Mammal::Mammal(string name, string vocalization, string food, float foodQuantity, string furColor, string order) : Animal(name,vocalization,food,foodQuantity) {
    this->name=name;
    this->vocalization=vocalization;
    this->food=food;
    this->foodQuantity=foodQuantity;
    this->order=order;
    this->furColor=furColor;
}

string Mammal::getOrder() {
    return order;
}

class Antelope : public Mammal {
   public:
	string species;
	string getSpecies();
	using Mammal::Mammal;
};

string Antelope::getSpecies() {
    return species;
}

int main() {

    Antelope jumper("Jenny","urk","hay",12.0,"tan","artiodactyla");
    jumper.species="springbok";
    cout<<"I'm a "<<jumper.getSpecies()<<" ",jumper.printme();
    cout<<"My fur is "<<jumper.furColor<<".\n";

    Reptile lizard("Jimmy","silent","bugs",0.5,"green","lepidosaur");
    cout<<"I'm a "<<lizard.getOrder()<<" ",lizard.printme();

    return 0;
}

Extra Exercises Clean up the solution to Example 2 by declaring attributes private or protected and implementing all required accessors and mutators.

Optionally, implement Animal in its own interface and implementation files. Include its implementation header into the source with

#include "animal.h"

You will need to compile your source files separately and link animal.o appropriately.

Previous
Next