Inheritance

📚 Gaddis (Ch. 15)

Inheritance

Establishes an “is-a” relationship:

  • A flower is a plant.
  • An ale is a beer.
  • A lager is a beer.
  • A truck is a vehicle.
    • A dump truck is a truck.
  • A mammal is a(n) animal.
    • A dog is a mammal.
      • A poodle is a dog.

… Infinite examples: The real world works this way!

Terminology and Notation

  • Base class (or parent): the more general class… the “starting point”.

  • Derived class (or child): more specialized, derived from base class.

  • Notation:

Base and derived class headers.

How “is-a” Works

The derived class object “is a(n)” object of the base class.


  • Has all characteristics of base class!

    • All members defined in base class.
    • Plus all members defined in derived class.


  • Derived class objects can “use” (access):

    • All public or protected members of base class.
    • All members of derived class.

How “is-a” Works

Base and derived class.

The physical object for a derived class object includes all parts of the base class, even though the private base class members are not directly accessible to derived class methods.

Wait… protected?

  • protected : access specifier, similar to private
    • Except: protected members can be accessed by derived classes!
    • Useful when you know you are writing a base class.


  • private data from base class is part of derived class, but cannot be accessed.

Class Access Specifiers

You may also inherit with a selected maximum level of access:

  • public
    • True “is-a” relationship.
    • Does not change any member’s downstream access level.
  • protected
    • public items from base class become protected in derived class.
  • private
    • public and protected items from base class become private in derived class.
    • Derived class object cannot be treated as an object of the base class.

Class Access Specifiers

Diagram of effect of access specifiers.

Building and Tearing-Down

  • Base class must be constructed before derived class.

  • Derived class must be destructed before the base class.

  • Derived class may pass arguments to base class constructor:

    Square::Square(int s) : Rectangle{s, s} { }

You must use the constructor’s member initialization list for this.

Detailed Example: A Square by inheriting from a Rectangle


A Square is just a rectangle with the additional constraint that the width and height are identical.


class Rectangle{
public:
    Rectangle(int w, int h) : width{w}, height{h} {}
    int  get_width()  const;
    int  get_height() const;
    int  get_area()   const;
    void print()      const;
    void set_width(int w);
    void set_height(int w);

private:
    int width;
    int height;
};
class Rectangle{
public:
    Rectangle(int w, int h) : width{w}, height{h} {}
    int  get_width()  const;
    int  get_height() const;
    int  get_area()   const;
    void print()      const;
    void set_width(int w);
    void set_height(int w);

private:
    int width;
    int height;
};

class Square : public Rectangle{
public:
    Square(int s) : Rectangle{s, s} {}
};

Simple! But, it leads to an issue.

class Square : public Rectangle{
public:
    Square(int s) : Rectangle{s, s} {}
};


Simple! But, it leads to an issue.

Square s1{3}; // 3x3 square
s1.print();   // "w=3, h=3"
s1.set_width(4);
s1.print();   // "w=4, h=3" ERROR

We inherited by public from Rectangle, so we can use any Rectangle method.

Bad news - this lets us make a mistake by setting width/height independently.

How can we fix this problem?

What if we define a “safe” way to set the side length?

class Square : public Rectangle{
public:
    Square(int s) : Rectangle{s, s} {}
    void set_side_length(int s) {
        set_width(s); 
        set_height(s); 
    }
};


Square s1{3}; // 3x3 square
s1.print();   // "w=3, h=3"
s1.set_side_length(4)
s1.print();   // "w=4, h=4" Better...

What if we define a “safe” way to set the side length?

class Square : public Rectangle{
public:
    Square(int s) : Rectangle{s, s} {}
    void set_side_length(int s) {
        set_width(s); 
        set_height(s); 
    }
};


Square s1{3}; // 3x3 square
s1.print();   // "w=3, h=3"
s1.set_side_length(4)
s1.print();   // "w=4, h=4" Better...
s1.set_width(5);
s1.print();   // "w=5, h=4" ERROR STILL!


The new method allows us to do the right thing, but it does not prevent us from doing the wrong thing. We can do better…

What if we redefine the behavior of the set methods for width/height?

class Square : public Rectangle{
public:
    Square(int s) : Rectangle{s, s} {}
    void set_side_length(int s);  // sets both width and height
    void set_width(int w)  { set_side_length(w); }
    void set_height(int h) { set_side_length(h); }
};


Square s1{3}; // 3x3 square
s1.print();   // "w=3, h=3"
s1.set_side_length(4)
s1.print();   // "w=4, h=4" Better...
s1.set_width(5);
s1.print();   // "w=5, h=5" FIXED!


Now, we are able to interact with the object in any way we expect, but it will always do the “right” thing. It prevents us from ending up in an inconsistent state.

class Square : public Rectangle{
public:
    Square(int s) : Rectangle{s, s} {}
    void set_side_length(int s);  // sets both width and height
    void set_width(int w)  { set_side_length(w); }
    void set_height(int h) { set_side_length(h); }
};


What are the benefits / problems of this solution?


What about aggregation?


What about protected inheritance?

What about protected inheritance?


class Square : protected Rectangle{
public:
    Square(int s) : Rectangle{s, s} {}
    int  get_side_length() const { return get_width(); }
    int  get_area()        const { return Rectangle::get_area(); }
    void print()           const { Rectangle::print(); }
    void set_side_length(int s) { 
        set_width(s); 
        set_height(s); 
    }
};


  • Forces us to redefine any methods we want in our public interface…

  • But if the overlap isn’t too large, that should not be too many.

  • Also, the redefined methods are mostly simple “pass-through” methods.

class Square : protected Rectangle{
public:
    Square(int s) : Rectangle{s, s} {}
    int  get_side_length() const;
    int  get_area()        const;
    void print()           const;
    void set_side_length(int s);
};

Are there any remaining problems?


Maybe print() needs to be customized:

void Square::print() const{
    cout << "s=" << get_side_length();
}


There might still be a monster lurking in the bushes…

But we’ll see more about that soon…

Inheritance