Inheritance

Inheritance without virtual functions

  1. Class derived has all the data members and member functions that class base has, plus more.
    Class base has a rudimentary member function print.
    Class derived has a bigger and better member function print.
    1. announcer.h
    2. base.h: has two data members, b1 and b2
    3. derived.h: has two more data members, d1 and d2. The bigger and better derived::print member function calls the original base::print member function to do some of its work, like one operator function calls another.
    4. main.C, main.txt
    c++ main.C
    
  2. Three generations: a class, a child class, and a grandchild class. Each class has an additional member function.
    1. cricket.h
    2. metric_cricket.h: a child class.
    3. kelvin_cricket.h: a grandchild class.
    4. main1.C, main1.txt
    c++ main1.C
    
  3. Four generations. Each class has an additional data member.
    1. amniote.h
    2. synapsid.h
    3. mammal.h
    4. placental.h
    5. main.C, main.txt
    c++ main.C
    

Virtual functions and polymorphism

  1. With a pointer to an object, you can call all the member functions of the object. In fact, that’s the most common way to call them; ¶ 8 and 9 below are intended to convince you of this. Another example: the pointer t to the terminal object will often be used to call the member functions of that object; see classes wolf and rabbit in ¶ 18 below.

    A pointer to an object takes the same -> operator that we used with a pointer to a struct. (This example also shows a reference to an object.)

    1. base.h
    2. main.C, main.txt
  2. A derived object delivers all the functionality of a base object, plus more. This means that a derived object actually is a fully functional base object, that happens to deliver even more functionality. An object that belongs to class derived therefore also belongs to class base. That’s why a base *p pointer can hold the address of a derived object as well as the address of a base object. (Similarly, a base& r reference can refer to a derived object as well as a base object.)

    To sum up: a base *p pointer has the flexibility to point at either a base object or a derived object. I admit that the following main.C has no need of this flexibility yet, because it has only one object.

    The expression p->print() in main.C has to decide whether to call base::print or derived::print. Making this choice is called binding. In the absence of the keyword virtual, the binding is performed when the program is compiled. This is called early binding or static binding. When performing early binding, the choice is determined by the data type of the pointer p. Since p was declared to be a “pointer to base” the decision of the early binding will be to call base::print, which is not the best choice for the derived object d. I wish it had decided to call derived::print.

    1. base.h
    2. derived.h
    3. main.C, main.txt
    c++ main1.C
    
  3. In the presence of the keyword virtual, the binding will be performed as the program runs. This is called late binding, or dynamic binding. The choice is now determined by the data type of the object in memory that p points to (or that r refers to) as the program executes the statement that calls print. To achieve late binding, make the three changes in red. The last two calls to print in main will now call derived::print, which is the better choice for the derived object d.
    	//in base.h
    
    	virtual void print() const {cout << b;}   //Adequate for class base.
    
    	//in base.h
    	//If a class has a virtual member function, it must
    	//also have a virtual destructor.
    
    	virtual ~base() {}
    
    You can also add the keyword override to the declaration of the print member function of class derived.h.
    	//in derived.h
    	void print() const override { //bigger and better print member function
    		base::print();
                    cout << " " << d;
            }
    
    1. base.h
    2. derived.h
    3. main.C, main.txt
  4. This time, we don’t know which object is pointed to.
    We don’t even know the class of the object that is pointed to.
    1. base.h
    2. derived.h
    3. main2.C, main2a.txt, main2b.txt
    c++ main2.C
    
  5. During each iteration of the for loop, the pointer p points to an object of a different class.
    1. base.h
    2. derived.h
    3. main3.C, main3.txt
    c++ main3.C
    
  6. During each function call, the argument of f points to an object of a different class.
    (g is the same example, but with a reference argument instead of a pointer argument.)
    1. base.h
    2. derived.h
    3. main4.C, main4.txt
    c++ main4.C
    
  7. Warning about passing an object to a function: you can pass either a base or derived object to the function f in this main5.C, but a derived object will be sliced down to a base when it arrives in f.
    1. base.h
    2. derived.h
    3. main5.C, main5.txt
    c++ main5.C
    
  8. I’d like to give a rudementary operator<< to class base, and a bigger and better operator<< to class derived. But an operator<< has to be a friend function, and only member functions can be virtual. Here’s the workaround that gets the effect of having a virtual friend operator<<.
    1. base.h
    2. derived.h
    3. main.C, main.txt
    c++ main.C
    
  9. interesting.C, interesting.txt does not use inheritance. That’s why the amount member function needs all those if statements.

    The following class tax is a base class with five derived classes.

    1. tax.h
    2. income.h
    3. capital_gains.h
    4. regressive.h
    5. progressive.h
    6. property.h
    7. main.C, main.txt
  10. What would go wrong if we forgot to give a virtual destructor to the base class? Everything is still okay in the following main1.C.
    1. base.h forgets to make the destructor virtual
    2. main1.C, main1.txt
    c++ main1.C
    
    But something can go wrong in the following main2.C.
    1. base.h forgets to make the destructor virtual
    2. derived.h
    3. main2.C, main2.txt
    c++ main2.C
    
    If we construct an object that was merely a base, everything is okay:
    constructing base
    ----------------------------------
    destructing base
    
    But if we construct a derived object, i.e., an object that is both a base and a derived, the delete statement will call the wrong destructor:
    constructing base
    constructing derived
    ----------------------------------
    destructing base
    
    The problem goes away if the base destructor is virtual:
    constructing base
    constructing derived
    ----------------------------------
    destructing derived
    destructing base
    

Can we recognize the virtual functions?

  1. It’s hard to predict which member functions of the base class will need to be declared virtual. The base class, rocket, was written in 1904, on the eve of Special Relativity. (Not intended to be a serious example.) No one could have predicted that its member function length would need to be virtual. A class can’t have a data member and a member function with the same name, so I added underscores.
    1. rocket.h
    2. relativistic_rocket.h: the derived class has a bigger and better version of the length member function.
    3. relativistic_rocket.C
    4. main.C, main.txt
    c++ main.C relativistic_rocket.C
    
  2. I’m going to quote myself:
    As shown above, there may be no way to tell in advance which member functions of the base class should be marked as virtual. But the real difficulty is much worse. There may be no way to tell in advance how the code in the base class should be divided up into member functions. The correct partitioning becomes obvious only when it is too late, after the incorrect design has been engraved in granite.

    —Mark Meretzky

    The following class date can’t tell the difference between leap years and non-leap years. But it is intended to be the base class for a smarter derived class that can tell the difference. It looks like the three big member functions install, operator++, operator-- will have to be replaced by smarter versions of themselves in the derived class, so these three functions have been declared to be virtual.

    1. date.h
    2. date.C
    3. main.C, main.txt (wrong output)
    c++ main.C date.C
    

    But we don’t need to write all of install, operator++, operator-- in the derived class. Only one little expression in these three member functions needs to be rewritten: length[m]. Let’s package this little expression in a separate member function, which will be the only member function of the base class that will need to be virtual.

    1. date.h
    2. date.C
    3. leapdate.h
    4. leapdate.C
    5. main.C, main.txt
    c++ main.C date.C leapdate.C
    
  3. Create an even smarter grandchild class of the above class date that knows there was no Year Zero between 1 BC and 1 AD.
  4. Create an even smarter great-grandchild class of the above class date that knows about the month in which the English speaking world converted from the Julian to the Gregorian calendar.
    cal 9 1752
    
       September 1752
    Su Mo Tu We Th Fr Sa
           1  2 14 15 16
    17 18 19 20 21 22 23
    24 25 26 27 28 29 30
    

A video game with three objects

  1. A video game that will be implemented using inheritance.
    1. term.h. This header file is written to be legal in both C and C++.
    2. term.c lowercase .c for the language C
    3. terminal.h
    4. terminal.C
    5. wolf.h
    6. wolf.C
    7. rabbit.h
    8. rabbit.C
    9. main1.C
    Minus lowercase L for the curses library on storm.cis.fordham.edu.
    cc -DUNIX= -c term.c
    ls -l term.o
    
    c++ main1.C wolf.C rabbit.C terminal.C term.o -lcurses
    ls -l a.out
    
    ./a.out
    
    Press h, j, k, l to move the wolf left, down, up, right.
  2. Exercise. Define a destructor for class wolf and a destructor for class rabbit. Here’s what they should do:
    1. Beep the terminal which the dying animal inhabits. (See wolf::move in wolf.C for an example of how to beep.)
    2. Pause for one second, so the dying animal stands “frozen in the headlights”. (See the main function in main1.C for an example of how to wait.)
    3. Call the get member function of the dying animal’s terminal to see if the animal’s location on the screen is occupied by the animal’s character. If so, call the put member function of the animal’s terminal to wipe the animal’s character off the screen by displaying the terminal’s background character there. (See wolf::move in wolf.C for an example of put and get, and the constructor for class wolf in wolf.C for an example of getting the background character.) If the dying animal’s location on the screen is not occupied by the animal’s character, do not call put, because the location is already occupied by another animal. Remember, there is one occasion when two animals are momentarily at the same place at the same time: right after the wolf stomps on the rabbit.
    The destructor should not change the value of any of the dying animal’s data members. There would be no point in doing so, since the animal is about to evaporate. Changing its data members would be like rearranging the deck chairs on the Titanic.

An abstract base class with pure virtual functions

  1. We have implemented class date with three int data members here (year, month, day), or with only one int data member here (day). We might want to to have both classes in the same program.

    Some of the code in these two classes was the same. To avoid writing the same code twice, let’s derive two classes (named date3 and date1) from a common base class named date0 that has no data members. Class date0 is intended only as a building block for the derived classes. A date0 object would be almost totally useless. Most of its member functions (prefix operator++, print) simply output error messages. It would be premature to attempt to write these member functions here in class date0. (Oddly, there is one member function (operator+=) that we can write, even though we have no data members to work with yet.)

    1. date0.h
    2. date0.C.
    3. main0.C, main0.txt. The only things we can do with a date0 object are to construct it, apply += 0 to it, and destruct it.
    c++ main0.C date0.C
    

    Here is a program that derives two classes (date1 and date3) from the base class date0.

    1. date0.h (we saw this class above)
    2. date0.C
    3. date1.h
    4. date1.C
    5. date3.h
    6. date3.C
    7. main2.C, main2.txt
    c++ main2.C date0.C date1.C date3.C
    
  2. Here is a easier way to declare the two “premature” member functions prefix operator++ and print in class date0:
    	//In date0.h.
    	//Remove the definitions of these two functions from date0.C.
    
    	//Class date0 is now an "abstract base class"
    	//because it has "pure" virtual functions:
    
    	virtual void print(ostream& ost) const = 0;
    	virtual date0& operator++() = 0;
    
    1. date0.h. Prefix operator++ and print are now pure virtual functions.
    2. date0.C. Prefix operator++ and print are now pure virtual functions.
    3. date1.h
    4. date1.C
    5. date3.h
    6. date3.C
    7. main.C, main.txt
    c++ main3.C date0.C date1.C date3.C
    

    Think of an abstract base class as a class with one or more missing pieces. How long does a class stay abstract?

A list

  1. In vectorint.C, we saw that we could add new elements to the end of a vector<int>. In the same way, we can insert new elements anywhere in a list<int>, not just at the end, and also delete any of the existing elements.
    1. list1.C, list1.txt illustrates insertion.
    2. list2.C, list2.txt illustrates deletion.

Create the animals using inheritance.

  1. Call the static member functions of class numeric_limits<int> to find the maximum and minimum values that can be stored into a variable of type int on our machine.
    1. limits.C, limits.txt
    Exercise.
  2. Derive classes wolf and rabbit from a base class wabbit. Now that each species of animal (class wolf and class rabbit) is derived from a common base class (class wabbit), we can store all the animals in one big master list. Very little code remains down in classes wolf and rabbit; we have achied “code reuse”.
    1. term.h
    2. term.c in the language C
    3. terminal.h
    4. terminal.C
    5. wabbit.h now has constructor and destructor, pure virtual functions, and a master list
    6. wabbit.C
    7. wolf.h
    8. wolf.C
    9. rabbit.h
    10. rabbit.C
    11. main.C puts each wabbit into its own block of dynamically allocated memory, allowing them to be killed off one by one in an unpredictable order.
    cc -DUNIX= -c term.c
    ls -l term.o
    
    c++ main.C wabbit.C wolf.C rabbit.C terminal.C term.o -lcurses
    ls -l a.out
    
    ./a.out
    

Multiple inheritance and virtual base classes

  1. Multiple inheritance. A C++ class can be derived from more than one base class.
    1. cowboy.h: a base class
    2. bank.h: another base class
    3. cowboybank.h: the derived class, created from two base classes.
    4. main.C, main.txt
    c++ main.C
    
  2. Now that we have multiple inheritance, a grandchild class (class window_with_horizontal_and_vertical) can inherit DNA from the same grandparent (class window) along two different bloodlines. Unfortunately, the grandchild ends up with two copies of the grandparent.
    1. window.h: the grandparent class
    2. window_with_horizontal.h: “mother”
    3. window_with_vertical.h: “father”
    4. window_with_horizontal_and_vertical.h: the grandchild class has two copies of the grandparent.
    5. main.C, main.txt
    c++ main.C
    
  3. To get a grandchild that contains only one copy of the grandparent, insert the keyword virtual into the mother and the father, and have the grandchild’s constructor call the grandparent’s constructor. In the diamond diagram on page 549 (page 77 of the PDF file), the grandchild inherits only one copy of its grandparent, the window, thanks to the keyword virtual in the mother and father.
    1. window.h: the grandparent class
    2. window_with_horizontal.h: “mother” is now virtual
    3. window_with_vertical.h: “father” is now virtual
    4. window_with_horizontal_and_vertical.h: the grandchild class now has only one copy of the grandparent.
    5. main.C, main.txt
    c++ main.C
    
    Also consider:
  4. A base class (class stack) with two derived classes. We will expand class stack in two different directions.
    To demonstrate class stack,
    1. stack.h. The base class is a stack that stores and retrieves ints. It has two static data members, like the length static data member of class date. Its static member function capacity receives no invisible pointer.
    2. main1.C, main1.txt.
      c++ main1.C
      
    To demonstrate classes stack, stackt, stacke,
    1. stack.h
    2. stackt.h. Class stack augmented with tracing.
    3. stacke.h and stacke.C. Class stack augmented with error checking.
    4. main2.C, main2.txt.
    c++ main2.C stacke.C
    
  5. Let’s add a grandchild class to the above example. Before we had multiple inheritance, it seemed reasonable to divide the code in the parent classes stackt and stacke into two major member functions, push and pop. But if we kept this division, there would be no way to write the member functions of the grandchild class correctly. For example, the following grandchild function stackte::push would accidentally call the gransparent function stack::push twice.
    void stackte::push(int i)  //Doesn't work correctly!
    {
    	stacke::push(i);   //Call the mother's push.
    	stackt::push(i);   //Call the father's push.
    }
    
    1. stack.h. The C++ standard library already has a class named std::stack<>.
    2. stackt.h: the mother class has the keyword virtual
    3. stacke.h: the father class has the keyword virtual
    4. stacke.C
    5. stackte.h
    6. stackte.C
    7. main.C, main.C
  6. Multiple inheritance without virtual base classes, page 557 (page 85 of the PDF file).
  7. Mix and match the ancestor classes page 563 (page 91 of the PDF file).

Public vs. private inheritance

    1. cricket.h
    2. metric_cricket.h: a child class.
    3. main2.C: a pointer and a reference to an object
    c++ main2.C
    
    Which statements would no longer compile if we change the public inheritance to private inheritance in metric_cricket.h?
    1. Public inheritance is also called interface inheritance or type inheritance.
    2. private inheritance is also called implementation inheritance.