Inheritance

  1. Inheritance without virtual functions. 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
    3. derived.h: has two more data members
    4. main.C
    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
    5. main2.C: a pointer and a reference to an object
    c++ main1.C
    
  3. Four generations. Each one has an additional data member.
    1. amniote.h
    2. synapsid.h
    3. mammal.h
    4. placental.h
    5. main.C
    c++ main.C
    
  4. Virtual functions. Without the keyword virtual our choice of which function to call is held hostage to the data type of the pointer p or the reference r.
    1. base.h
    2. derived.h
    3. main1.C
    c++ main1.C
    
    10 20
    10
    10
    
    To achieve late binding, a.k.a. dynamic binding, add they keyword virtual to the declaration of the print member function of class base.h.
    	//Adequate for class base, can be overridden later.
    
    	virtual void print() const {
    		cout << i1;
    	}
    
    	//If a class has a virtual member function, it must
    	//also have a virtual destructor.
    
    	virtual ~base() {}
    
    You can also add they keyword override to the declaration of the print member function of class derived.h.
    	//A bigger and better print member function.
    
    	void print() const override {
    		base::print();
                    cout << " " << i2;
            }
    
    10 20
    10 20
    10 20
    
  5. 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.
    Using the classes base.h and derived.h from the previous example, assuming the keyword virtual inserted before base::print.
    1. base.h
    2. derived.h
    3. main2.C
    c++ main2.C
    
    10
    
    20 30
    
  6. During each iteration of the loop, the pointer points to an object of a different class.
    1. base.h
    2. derived.h
    3. main3.C
    c++ main3.C
    
    10
    20 30
    21 31
    11
    
  7. 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
    c++ main4.C
    
    10
    20 30
    10
    20 30
    
  8. Warning: 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
    c++ main5.C
    
    10
    20
    
  9. Only a member function, not a friend, can be declared virtual. Here’s the workaround that gets the effect of having a virtual friend operator<<.
    1. base.h
    2. derived.h
    3. main.C
    c++ main.C
    
    10
    20 30
    
  10. Class vehicle is a base class with two derived classes.
    1. interesting.C
    2. vehicle.h
    3. car.h
    4. motorcycle.h
    5. main.C
    c++ main.C
    
    The car goes beep beep!
    Fun fact: The world's first speeding ticket was issued in 1902!
    
    The motorcycle goes vroom vroom!
    Fun fact: The first motorcycle was invented in 1885!
    
    
  11. Class tax is a base class with five derived classes.
    1. interesting2.C
    2. tax.h
    3. income.h
    4. capital_gains.h
    5. regressive.h
    6. progressive.h
    7. property.h
    8. main.C
  12. 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
    2. main1.C
    c++ main1.C
    
    constructing base
    ----------------------------------
    destructing base
    
    But something can go wrong in the following main2.C.
    1. base.h
    2. derived.h
    3. main2.C
    c++ main2.C
    
    If we constructed an object that was merely a base, everything is okay:
    constructing base
    ----------------------------------
    destructing base
    
    But if we constructed a derived object, i.e., an object that was 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 derived destructor is virtual:
    constructing base
    constructing derived
    ----------------------------------
    destructing derived
    destructing base
    
  13. Difficult to predict which member functions of the base class will need to be declared virtual. (Silly example.)
    1. rocket.h. The base class, rocket, was written in 1904, on the eve of Special Relativity. No one could have predicted that the 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.
    2. relativistic_rocket.h and relativistic_rocket.C, the derived class, has a bigger and better version of the length function.
    3. main.C
    c++ main.C relativistic_rocket.C
    
    velocity == 0.000000e+00, length == 1.000000
    velocity == 2.596279e+08, length == 0.500000
    velocity == 2.902728e+08, length == 0.250000
    velocity == 2.974411e+08, length == 0.125000
    Velocity 2.99792e+08 can't be >= the speed of light 2.99792e+08.
    
  14. 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
    c++ main.C date.C
    
    2/29/2025     (wrong output)
    

    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 its own 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
    c++ main.C date.C leapdate.C
    
    2/29/2025    (Class date still doesn't know about leap years.)
    3/1/2025     (Class leapdate does know.)
    
  15. Create an even smarter grandchild class of the above class date that knows there was no Year Zero between 1 BC and 1 AD.
  16. 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
    
  17. An inheritance game.
    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.
  18. 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.
  19. array.C: three ways to loop through an array, a vector, or a list.
    Exercise. In the main function in main1.C, replace the rabbit object with an array (or a vector<rabbit>) of three rabbit objects (or as many as you want). For example,
            rabbit a[] {
                    {term, 10, 20},   //Please use different numbers, not mine.
                    {term, 30, 40},
                    {term, 50, 60}
            };
    
            const size_t n {size(a)}; //need <iostream> for size
    
    	vector<rabbit> v {        //and #include <vector>
                    {term, 10, 20},   //Please use different numbers, not mine.
                    {term, 30, 40},
                    {term, 50, 60}
            };
    
    In place of the existing statement that calls the move member function of the rabbit, write a loop that will call the move member function of each rabbit in the array (or in the vector<rabbit>). You can let the game end as soon as any rabbit is eaten by the wolf. And now that you have more than one rabbit, change the victory message in main1.C to “You killed a rabbit!”. If you’re totally mystified, you can peek at main2.C.
  20. Loop through a vector or a list with an iterator. You would have to do this if you were inserting or deleting items while you were looping.
    1. iterator.C
  21. An abstract base class with pure virtual functions. We have implemented class date with three int data members here (year, month, day), or with only one int data member here (day). 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.
    1. date0.h
    2. date0.C. Most of the member functions (prefix operator++, print) simply output error messages. It would be premature to attemptto 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.
    3. main0.C. The only thing we can do with a date0 object is to construct it, apply += 0 to it, and destruct it.
    c++ main0.C date0.C
    
    
    
    1. date0.h
    2. date0.C
    3. date3.h
    4. date3.C
    5. date1.h
    6. date1.C
    7. main2.C
    c++ main2.C date0.C date1.C date3.C
    
    4/10/2025
    1/15/2026
    
    4/10/2025
    1/15/2026
    
    sizeof (int)   ==  4
    sizeof (date0) ==  8
    sizeof (date1) == 16
    sizeof (date3) == 24
    
    A nicer way to declare the two “premature” member functions prefix operator++ and print of class date0:
    //In date0.h. Remove the definitions of these two functions from date0.C.
    
    	//Class date0 is now an "abstract" base class
    	//becaue it has "pure" virtual functions:
    
    	virtual void print(ostream& ost) const = 0;
    	virtual date0& operator++() = 0;
    
    1. date0.h. Assume that prefix operator++ and print are now pure virtual functions.
    2. date0.C. Assume that prefix operator++ and print are now pure virtual functions.
    3. date1.h
    4. date1.C
    5. date3.h
    6. date3.C
    7. main3.C
    g++ main3.C date0.C date1.C date3.C
    
    4/10/2025
    4/10/2025
    
    How long does a class stay abstract?
  22. Derive classes wolf and rabbit from a base class wabbit.
    1. term.h
    2. term.c in the language C
    3. terminal.h
    4. terminal.C
    5. wabbit.h
    6. wabbit.C
    7. wolf.h
    8. wolf.C
    9. rabbit.h
    10. rabbit.C
    11. main.C
    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
    
  23. 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.
    4. main.C
    c++ main.C
    
    Gimme a chaw 'a 'baccy.
    Please take a deposit slip.
    Time to clear out of town.
    Put 'em up, pardner!
    Your account is overdrawn.
    
  24. A virtual base class. Now that we have multiple inheritance, a C++ class can inherit DNA from the same ancestor along two different bloodlines. 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: the mother class
    3. window_with_vertical.h: the father class
    4. window_with_horizontal_and_vertical.h: the grandchild class. Its constructor can call the constructor for the grandparent.
    5. main.C
    c++ main.C 
    
    construct window 10
    construct window_with_horizontal 10 20
    construct window_with_vertical 10 30
    construct window_with_horizontal_and_vertical 10 20 30 40
    
    destruct window_with_horizontal_and_vertical 40
    destruct window_with_vertical 30
    destruct window_with_horizontal 20
    destruct window 10
    
    If you remove the virtuals from the mother and father, the grandchild will inherit two copies of the grandparent. See the diagrams on page 553 (page 81 of the PDF file). In this case you must also remove the grandparent’s call to the constructor for the granschild.
    construct window 10
    construct window_with_horizontal 10 20
    construct window 10
    construct window_with_vertical 10 30
    construct window_with_horizontal_and_vertical 10 20 30 40
    
    destruct window_with_horizontal_and_vertical 40
    destruct window_with_vertical 30
    destruct window 10
    destruct window_with_horizontal 20
    destruct window 10
    
    Also consider:
    1. a bacon cheeseburger containing one hamburger
    2. a nurse practitioner midwife vs. “a stamp and a clamp”
  25. A base class (class stack) with two derived classes. We will expand class stack in two different directions.
    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. Demonstrate class stack.
      c++ main1.C
      
    3. stackt.h. Class stack augmented with tracing.
    4. stacke.h and stacke.C. Class stack augmented with error checking.
    5. main2.C. Demonstrate classes stack, stackt, stacke.
      c++ main2.C stacke.C
      
      stackt()
      10
      push(10)
      pop returns 10
      10
      10
      
      20
      push(20)
      pop returns 20
      20
      20
      ~stackt()
      
  26. 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
    c++ main.C
    
    stackt()
    push(10)
    pop(10)
    10
    can't pop stack with size 0
    
  27. Multiple inheritance without virtual base classes, page 557 (page 85 of the PDF file).
  28. Mix and match the ancestor classes page 563 (page 91 of the PDF file).
  29. 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
    
    celsius == 22.2222
    fahrenheit == 72
    fahrenheit == 72
    fahrenheit == 72
    
    What statements 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.