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!