Notes on Inheritance

(related reading: Main & Savitch, pp. 620-627, pp. 650-657; Wang, pp. 231-249)

Goal: Build new, but similar, data structures from old.

A possibility: Consider a simple implementation of a stack:
class Stack {
public:
  Stack() : count(0) {}
  void push(int i) {
    assert(count < 100);
    count++;
    data[count]=i;
   }
  int pop() { 
    assert(count > 0);
    count--;
    return data[count];
   }
private:
  size_t count;
  int data[100];
};
Suppose we decide to implement a top function. We might write:
class StackWithTop {
public:
  Stack() : count(0) {}
  void push(int i) {
    assert(count < 100);
    count++;
    data[count]=i;
  }
  int pop() { 
    assert(count > 0);
    count--; 
    return data[count];
  }
  int top() {
    assert(count > 0);
    return data[count-1];
  }
private:
  size_t count;
  int data[100];
};
Wait! There's a bug in the original code! We wanted:
class Stack {
public:
  Stack() : count(0) {}
  void push(int i) {
    assert(count < 100); 
    data[count]=i;
    count++;
  }
  int pop() { 
    assert(count > 0);
    count--;
    return data[count];
   }
private:
  size_t count;
  int data[100];
};
We can either recopy and rename, or change the mistake again. Either way, we have to do something redundantly.
class StackWithTop {
public:
  Stack() : count(0) {}
  void push(int i) {
    assert(count < 100);
    data[count]=i;
    count++;
  }
  int pop() { 
    assert(count > 0); 
    count--;
    return data[count];
  }
  int top() {
    assert(count > 0);
    return data[count-1];
   }
private:
  size_t count;
  int data[100];
};

Reuse rather than copy

Revised goal: Build new, but similar, data structures from old and avoid redundancy.

We have already seen several techniques for reusing code: Now we consider a new technique: inheritance.

Inheritance

Inheritance is a mechanism for reusing code with the added benefit of conceptual simplification. For example. A StackWithTop object is a Stack object with a top function:
class StackWithTop : public Stack {
public:
  int top() {
     assert(count > 0); return data[count-1]; }
};
Of course we need to make a small change in the class Stack to make this valid:
class Stack {
public:
  Stack() : count(0) {}
  void push(int i) {
    assert(count < 100); data[count]=i; count++; }
  int pop() { 
    assert(count > 0); count--; return data[count]; }
protected:
  size_t count;
  int data[100];
};

Inheritance comes in many flavors, each with its own subtleties. (Entire books and courses focus on inheritance.) Here we just consider one such flavor --- public inheritance --- and even so, only consider it in a simple form.

Public inheritance is most often used to capture the is a relationship.

We say that a derived class (subclass) inherits from a base class (superclass). A derived class inherits all public members of its base class as public members. A derived class may access any protected members of its base class. Protected members remain invisible to the outside world.

class Foo {
public: 
  int pub;
protected:
  int pro;
private:
  int pri;
};

class Bar : public Foo {
public:
  void bogus();
}

void Bar::bogus() 
{
  pub = 3; // ok
  pro = 2; // ok
  pri = 1; // ERROR! no access to private members of Foo
}

Bar b;

cout << b.pub; // ok
cout << b.pro; // ERROR: protected members can't be used outside class
cout << b.pri; // ERROR

Inheritance hierarchies

Overriding member functions

Rotating a circle is simpler than rotating other shapes. We'd like to be able to override the base version of rotate so that we circle's rotate is more efficient. We can do that by redefining the rotate function in the circle class.

Warning: never override the signature of a method (member function). Preserve: number of arguments, argument type, and return type.

Substitutability

With public inheritance, an object of the subclass may be substituted for an object of the superclass wherever the latter is acceptable. Note, the reverse is most certainly not true.
class Point {
public:
  Point(double yy, double xx) : y(yy), x(xx) {}
  Point(const Point& source); // copy con
protected:
  double y,x;
};

enum color {BLUE, GREEN, RED};

class ColorPoint : public Point {
public:
  ColorPoint(double yy, double xx, color cc) 
  : Point(yy, xx), c(cc) {}
  ColorPoint(const ColorPoint& source); // copy con
private:
  color c;
};

  Point p(1.4, 2.1);
  ColorPoint cp(3.4, 1.5, BLUE);

  Point p1(p);        // ok
  ColorPoint cp1(cp); // ok
  Point p2(cp);       // ok, p2 forgets about cp's color
  ColorPoint cp2(p);  // ERROR! what would cp2's color be?

A more practical example

We might want a list class template that keeps the items in order. We can achieve this using public inheritance and overriding the insert function:
template <class Item>
class OrderedList : public List<Item> { 
public:
  void insert(const Item& entry); // override List's insert
};


template<class Item>
void OrderedList<Item>::insert(const Item& entry)
{
  assert(!isFull());

  size_t i;
  for(i=0; i < count && data[i] < entry; i++);
  for(size_t j=i; j < count; j++)
    data[j+1] = data[j];
  data[i] = entry;
  count++;
}
Now, we can consider using inheritance to build more efficient binary search trees.