CS 112 - Week 14 Lecture 1 - 2022-11-29

TODAY WE WILL
*   Announcements
*   inheritance and the "big-3"
*   reminder: types and derived classes
*   leading into: polymorphism, and virtual methods,
    and dynamic late binding
*   prep for next class

*   reading: some of this is back to Savitch Ch. 15

====
How DOES inheritance impact the "Big 3"?
    destructor, copy constructor, overloaded assignment operator
====
*   see Savitch Ch. 15 for a *bit* deeper discussion of this

*   we already knew we needed to write constructor(s) for
    a derived class;
    (and call the base class' constructor as part of that)

*   Destructors are also effectively not inherited --
    if your derived class needs one (e.g., has dynamic
    memory to deallocate), you should write that.

*   Copy constructor is not inherited, but the automatically
    generated one is OK *unless*, again, dynamic memory
    is involved (or pointers that would lead lead to
    overly-shallow copies)

*   The overloaded assignment operator is also not
    inherited -- for a derived class, the automatically
    generated one will be the same as if it weren't a
    derived class, but if needed for dynamic memory
    reasons, you SHOULD define it;

*   if YOU DO need, in a derived class, to define
    the copy constructor and =,
    SHOULD call the base class' version of those
    to help:

    // copy constructor

    MyDerived::MyDerived(const MyDerived& object) : MyBase(object)
    {
        ...
    }

    // the assignment operator

    MyDerived& MyDerived::operator=(const MyDerived& right_side)
    {
        MyBase::operator=(right_side);

        // then handle the data fields particular to MyDerived
    }

*   what about a destructor, then?
    *   Savitch: If a base class has a correctly-functioning
        destructor, then when the destructor for a derived
	class is invoked, the system will AUTOMATICALLY
	invoke the destructor for the base class as well

	That is, your derived class destructor doesn't
	have to call the base class' destructor explicitly;

	the derived class destructor just needs to call delete
	for dynamic memory for data fields particular to that
	derived class;

=====
reminder of types of derived objects
=====
*   see types-reminder.cpp -- a ColorPoint is of type
    ColorPoint and also of type Point

    And so I can put ColorPoint objects in an array of
    type Point -- BUT they are then treated as type Point
    in method calls.

    ...this is so-called STATIC binding, also called
       EARLY binding

=====
Polymorphism
=====
*   generally: ability to associate MULTIPLE meanings
    to ONE method name

*   specifically, in object-oriented programming:
    refers to the abiulity to associate MULTIPLE meanings
    to ONE method name by means of a special mechanism
    called DYNAMIC binding (also called LATE binding)

    ...you don't get this by default!
    you have to declare such a method in a certain way,
    and call it in a certain way;
    ...the method needs to be declared as a virtual method.

*   SO: Savitch: "Thus, polymorphism, late binding, and virtual
    [methods] are all really the same topic"...!

*   SO....
    what's a virtual method?
    ...in a sense, you might say it is used before it is
       defined?!

    In a class definition, if you want a method to be
    virtual, put the word virtual before its name:

    class Blah
    {
        public:
	    ...
	    virtual double do_this(); // do_this is now a virtual
	                              //     method

        ...
    };

    *   FUN FACT:
        *   this word virtual MUST be in front of the
	    name of the method in the BASE class' declaration --

	    virtual does NOT have to be in the front of
	    that method's name in a derived class declaration,
	    although it CAN be and that's considered good
	    style to PUT it there;

    *   what does the word virtual ask the compiler to do?
    	you are asking that the C++ environment be able to
	WAIT until this method is called by a program,
	and THEN get the method implementation corresponding
	to the calling object

*   SO: in Point.h,
    we added virtual in front of the headers for
    display and to_string

    ...BUT, to get polymorphism, POINTERS need to be involved.

    for example:

    Point *point_ptrs[4];

    point_ptrs[0] = new Point(1, 1);
    point_ptrs[1] = new Point(2, 2);
    point_ptrs[2] = new ColorPoint(3, 3, "green");
    point_ptrs[3] = new ColorPoint(4, 4, "red");

    for (int i=0; i < 4; i++)
    {
       point_ptrs[i]->display();
    }

    // remember to free those dynamically-allocated
    //   Points and ColorPoints

    for (int i=0; i < 4; i++)
    {
        delete point_ptrs[i];
    }

=====
NOTE about the "slicing problem"
=====
*   (see Savitch for more about this)

*   the slicing problem is indeed what you see when you
    assign a derived object to a base object variable --
    the non-inherited parts are lost, "sliced" off;

*   BUT C++ offers a way to treat a derived class object
    as a base class object without losing its
    derived-class-specific parts.
    To do this, we use POINTERS to DYNAMIC OBJECT INSTANCES
    (combined with virtual methods)

    *   see example at end of types-reminder.cpp!