Inheritance Lecture 2 --------------------- Inheritance allows us to group classes into families of related types allowing for sharing of common operations and data. Dynamic binding (we'll learn in this lecture) allows us to program these families as a unit rather than as individual classes. Recap from last time: Inheritance defines a parent/child relationship. The parent defines the public interface and the class members that are common to all its children. Each child adds or overrides what it inherits to implement its own unique behavior. The parent is called the base class and the child is called the derived class. The relationship between the base class (parent) and its children is called an inheritance hierarchy. Dynamic binding --------------- Allows us to manipulate objects of an inheritance family in a type-independent manner. We program using a common interface through a pointer or reference of an abstract base class. The actual operation to invoke is not determined until run-time based on the type of the object actually addressed. We'll introduce these concepts using a simple example. Suppose that we've implemented some form or other of Stats, and now we want to start writing functions that take as arguments Stats. Like this one, that reads the entire contents of an input stream and feeds them to a Stats update: void process_grades(Stats& s) { double Val; while (cin >> Val) { s.update(Val); } s.print(); } Since this function works with only the public members of the Stats class (namely, the function member "update"), it does not need to be rewritten when we change the implementation of Stats. Except, what if we decide that we need several kinds of Stats at once? We need simpleStats (arithmetic Stats) for the straightforward summing of quantities that are approximately uniformly distributed (like say grades ? :-), and we may need some corrected type of Stats for non-uniformly distributed numbers and/or other statistics to help the correction. class simpleStats { private: unsigned Count; double Sum; public: simpleAverager(); void update(double Val); double print() const; }; class correctedStats { private: unsigned Count; unsigned CorrectedSum; public: correctedAverager(); void update(double Val); double print() const; }; Now, if we want to use both kinds of Stats in the same program, do we have to define "process_grades" twice? Or once for every kind of Stats that we use? And do we have to rewrite lots of functions that way? And what about a function that takes two Stats? Duplicating this process_grades function over and over would not only be annoying, but it would also lead to errors, as people got bored and left out something once in a while. It would be better if we could represent all that simpleStats and correctedStats have in common in one place, and their differences somewhere else. We can. What they have in common is an interface - the public part of a class. So, let's define a class that is only interface: class Stats { public: void update(double Val) {} double print() const {return 0.;} }; We have provided silly implementations of the function members here only for purposes of illustration later. We can make this class a *base class*, and the other two types *derived classes* from this base class, by using inheritance: The derived class consists of two parts: the base class data members (if any) and the derived class portion. class simpleStats: public Stats // specifying public inheritance here { private: unsigned Count; double Sum; public: simpleAverager(); void update(double Val); double print() const; }; Note that the Stats base class has no constructor because it has no data members to initialize. class correctedStats: public Stats { private: unsigned Count; unsigned CorrectedSum; public: // and so on }; simpleStats and correctedStats are specializations of Stats. Any function that can accept an Stats (by reference), can accept either a simpleStats or a correctedStats. It's also okay if the function is being passed the argument by pointer. But passing by value is no good - you can't copy an abstract Stats, any more than you can make one. You can only copy or make a specific kind of Stats (i.e., either simple or corrected Stats). Now, just to be clear, what happens if we have two Stats, of different derived types, derived from the same base type: simpleStats s; correctedStats cs; process_grades(s); Inside process_grades, when update is invoked, which update is it? Unfortunately, it is the one that goes with the Stats base class, which we implemented above as {} So that's no good. What we want is for simpleStats::update to be called when we pass a simpleStats to process_grades, and correctedStats::update to be called when the argument is a correctedStats. How do we do that? We have to add the keyword "virtual" to the function definitions on the base class: By default, a member function is resolved statically at compile-time. Prefacing the function declaration with the virtual keyword indicates that the member function will be resolved dynamically during run-time. class Stats { public: virtual void update(double Val) {} virtual double print() const {return 0.;} }; Now, the version of update or print that gets invoked depends on the type of Stats we passed (by reference or pointer) to the process_grades function. That's what virtual does. So, why did I bother to provide silly definitions for Stats::update and Stats::print? Mostly to make a point about the difference between virtual and pure virtual (see below). So I can lose those definitions. Going further, I can make them "pure virtual functions" by stating, in a syntactically peculiar way, that any class derived from this one *must* provide its own defintions of these function members. Making those "pure" virtual functions, and this class an "abstract" base class, requires it to look like this: class Stats { public: virtual void update(double Val) = 0; // pure virtual double print() const = 0; // pure }; Strange use of "=0". So we can pass (by pointer or reference) a specific kind of Stats (simple or corrected) to a function that expects an Stats, and everything will be ok. We don't need two or more versions of process_grades; one will do. Now, just for purposes of illustration, let's notice that both our Stats have a Count data member. Maybe we could put that common feature in the base class? We could: class Stats { private: // we'll have to change this line, eventually unsigned Count; public: Stats(); virtual void update(double Val) = 0; virtual double print() const = 0; }; Stats::Stats(){ Count = 0; } void Stats::update(double) { ++Count; } The derived class must provide an implementation of each of the pure virtual functions inherited from its base class (function print in this case). In addition it must declare those members that are unique to its class. In our example, each Stats overrides the update method present in the base class with their specific update implementation. For overriding to work, the prototype of the derived class instance must match the base class prototype exactly: the parameter list and the return type and whether the base class function is const or not. class simpleStats: public Stats { public: unsigned Sum; simpleStats(); void update() const; double print() const; }; How do we implement the constructor? simpleStats::simpleStats() : Stats() { // first, we initialize the base class with its // default constructor Sum = 0.0; // then we initialize the data member for this } // class, and we're done. Now, how do we implement update? void simpleStats::update(double Val) { Sum += Val; // now, how do we handle the part of update that includes Count that // we've "farmed out" to the base class? Stats::update(Val); // we call update, specifically refering to the base-class version // of update, not this version of update again. } double simpleStats::print() const { return Sum / (double)Count; } Now, there's one more thing we need to work out. Can I really access Stats::Count in simpleStats::print? No, because Count is a private member of that class. As specified so far, only members of the Stats class can access it. We want to be more flexible, so that not only class members but also derived class members (like the member print of the derived class simpleStats) can also access Count. So, we need to make Count not private, but protected, as described in the first lecture on inheritance. public: anyone can access it protected: only members of this and derived classes can access it private: only members of this class can access it. In more detail: Once we have declared Count as protected in the base class, any derived class, independent of the depth of the inheritance hierarchy can access Count as if it were its own data member. The public base class members are also public in the derived class and are available to users of the derived class. The protected base class members are protected within the derived class. They are available to classes inheriting from the derived class but not to users of the class. Finally, the derived class has no access privilege to the private base class members. So the declaration of the Stats class should look like this: class Stats { protected: unsigned Count; public Stats(); virtual void update(double Val) = 0; virtual double print() const = 0; }; Wrap-up and exam-style questions on Inheritance ----------------------------------------------- - Benefits of Inheritance a) Sharing - member functions and data - The parent defines the class members that are common to all its children b) Adding and Overriding - member functions and data - Each child *adds* or *overrides* what it inherits to implement its own unique behavior. c) Manipulating different objects as a group - In a type-independent manner - We program using a common interface through a pointer to the base class. - The operation invoked is not determined until run-time - Based on the type of the object actually addressed. Q: What does the following program output: class A { public: A(int i) : m_i(i) { cout << "A" << endl; } ~A() { cout<<"~A"<x(); /* outputs "A:x" !!*/ return 0; } - New function w() added to class B - x() function in B overrides x() function in A c) Manipulating different objects as a group -------------------------------------------- c.1) Virtual Functions ---------------------- class A { public: A(int i) : m_i(i) { cout << "A" << endl; } virtual ~A() { /* changed to virtual */ cout<<"~A"<y(); /* outputs "B:y"! */ return 0; } - Virtual functions are used to support multiple related objects with pointers (and references) - Function declared virtual in a base class - Can be overridden in derived class - Overriding only happens when signatures are the same - Otherwise new function overloads the function or the operator name - Virtual ensures that derived class function definition is resolved dynamically, e.g., destructors farther down the hierarchy get called Full example: int main() { B b(2, 3); A *ap = &b; B *bp = &b; b.x (); // prints "B:x" b.y (); // prints "B:y" bp->x (); // prints "B:x" bp->y (); // prints "B:y" ap->x (); // prints "A:x" ap->y (); // prints "B:y" return 0; }; - Virtual functions only matter with pointer or reference - Calls on object itself resolved statically ­ E.g., b.y(); - Look first at pointer/reference type - If non-virtual function, it is resolved statically - e.g., ap->x(); - If virtual function, it is resolved dynamically - e.g., ap->y(); - Note that virtual keyword need not be repeated in derived classes - But it's good style to do so - Caller can force static resolution of a virtual function via scope operator ­ e.g., ap->A::y(); prints "A::y" d) Summary: Tips on Inheritance ------------------------------- - Push common members and variables up into base classes for sharing - Add or override members in derived classes to define unique behavior - Use virtual member functions for dynamic typing - Use a base-class pointer (or reference) to manipulate objects in a group - Use abstract base classes to declare interfaces - Even though you don't have to, label each virtual method (and pure virtual method) in derived classes