52
STAT 598W: Lecture 12 Purdue University More on Inheritance and Related Topics

STAT 598W: Lecture 12

  • Upload
    cassia

  • View
    40

  • Download
    0

Embed Size (px)

DESCRIPTION

STAT 598W: Lecture 12. Purdue University More on Inheritance and Related Topics. Topics. Inheritance Polymorphism Virtual methods Abstract classes. Inheritance. Inheritance is the property that instances of a child class can access data and behavior of the parent class(s). - PowerPoint PPT Presentation

Citation preview

Page 1: STAT 598W: Lecture 12

STAT 598W: Lecture 12

Purdue UniversityMore on Inheritance and

Related Topics

Page 2: STAT 598W: Lecture 12

Topics

Inheritance Polymorphism Virtual methods Abstract classes

Page 3: STAT 598W: Lecture 12

Inheritance

Inheritance is the property that instances of a child class can access data and behavior of the parent class(s). Parent superclass; child subclass A CEO is a manager is an employee A dog is a mammal is an animal is a

living thing...

Page 4: STAT 598W: Lecture 12

Benefits of Inheritance Software reusability: inherited behavior

doesn't have to be rewritten, and thus will save time and likely be more reliable.

Code sharing: separate users use the same classes; two subclasses use facilities of a common superclass.

Consistent interface: by inheriting methods, the interface to subclasses will be largely consistent. Objects that are almost the same will have interfaces which are almost the same.

Page 5: STAT 598W: Lecture 12

Benefits of Inheritance Software components: “off-the-shelf”

software units, e.g., Microsoft's Microsoft Foundation Classes library, QuantLib, the Interactive Brokers C++ API.

Rapid prototyping: concentrate only on portions of a new system which are different than previous ones. Get something running sooner, for early evaluation.

Page 6: STAT 598W: Lecture 12

Benefits of Inheritance Polymorphism:

Software is typically designed top-down, written bottom-up. Inheritance encourages abstract superclasses, specialized for particular circumstances.

So rather than having only very low-level code reusable, code at the highest level of abstraction can be reused.

Polymorphic objects can react differently depending on the type of input they are given.

Page 7: STAT 598W: Lecture 12

Costs of Inheritance Execution speed: lots of function calls, very

general methods for dealing with arbitrary subclasses. We are getting better at this…

Program size: a library of objects may have just what you want, but it may be “spread out.” Purpose-built code will likely be smaller.

Other types of complexity: flow of control in OOP programs can be just as hard to trace.

Page 8: STAT 598W: Lecture 12

Implementation Details

Consider a class for employees:class Employee {public:   string name;   int age;   int department;   int salary;   Employee * next; // link to employee list   // ... };

This example comes from Stroustrup

Page 9: STAT 598W: Lecture 12

Managers Are Objects Too

So far, so good, but…

class Manager {   Employee emp;   // manager's employee record   Employee* group;    // people managed   int level; // high, higher, highest  // ... };

Page 10: STAT 598W: Lecture 12

A Problem, and a Solution A manager is an employee, so Employee data

is stored in the emp member of Manager. But how to get a Manager into the linked list of employees?

A Manager* is different from an Employee*. A better way: a Manager is an Employee, so

make it a subclass: class Manager : public Employee { // public inheritance   Employee* group;   short level;   // ... };

Page 11: STAT 598W: Lecture 12

Subclass Advantages

Now we can create a list of employees, some of whom are managers: Employee * makeList() {  

Manager m1, m2;   Employee e1, e2;   Employee * elist;   elist = &m1;  // put m1 on elist   m1.next = &e1;        // put e1 on elist   e1.next = &m2;        // put m2 on elist   m2.next = &e2;        // put e2 on elist   e2.next = 0;  // terminate elist return elist;}

Page 12: STAT 598W: Lecture 12

How Does This Work?

This works because a manager is an employee, so an Employee * can point to a Manager.

This doesn't work the other way around, unless there is explicit pointer type conversion. (Which is really really dangerous!)

Page 13: STAT 598W: Lecture 12

Let's Add Some Methods

class Employee {   string name;   // ... public:   Employee* next;   void print();   // ... }; class Manager : public Employee {   // ... public:   void print();   // ... };

Page 14: STAT 598W: Lecture 12

This Would Seem Natural

But derived classes can't access private portions of the base class. Why? If they could, then private stuff wouldn't

really be private. Anyone could construct a derived class

and have access.

void Manager::print() {  cout << "name is " << name << '\n'; // error }

Page 15: STAT 598W: Lecture 12

The Good Solution Derived classes cannot access private

members of the base class. But they can access public members:

Note the use of the scope resolution operator, needed since print() is being redefined in Manager. (What happens if we forget to say Employee::?)

void Manager::print() {   Employee::print();    // print employee info, then   // print manager info }

Page 16: STAT 598W: Lecture 12

The Not-So-Good Solution

Make name a protected member of the Employee class, so subclasses (like Manager) have access

This is generally a bad idea, but everyone does it.

Why bad? It breaks encapsulation; anyone can write a subclass to get access.

Page 17: STAT 598W: Lecture 12

The Inevitable Student Class

#include <iostream>#include <string>using namespace std;

enum studentYear {freshman, sophomore, junior, senior, graduate};

class Student {protected: int studentID; double gpa; studentYear y; string name;public: Student(int id, double g, studentYear x, string nm); void print() const ;};

Page 18: STAT 598W: Lecture 12

GradStudents are Students

enum support { ta, ra, fellow, other};

class GradStudent: public Student {protected: support s; string dept; string thesis;public: GradStudent(int id, double g, year x, string nm, support t, string d, string th); void print() const;};

Student members have to be protected for this to work

Page 19: STAT 598W: Lecture 12

New Concepts Which base class members are accessible to

derived classes? The choices are public, protected, private.

public means that protected and public members of Student are available to GradStudent. This is the normal (but not default) case.

Note that private members of the base class are never available to subclasses. Why?

It is typical for a subclass to add new members, both data and methods. Note print() is an overridden method, a concept different from overloading. The signature is the same, but the owner is different.

Page 20: STAT 598W: Lecture 12

Code Reuse With Inheritance

Student::Student(int id, double g, studentYear x, string nm) :studentID(id), gpa(g), y(x), name(nm) {} GradStudent::GradStudent(int id, double g, studentYear x, string nm, support t, string d, string th)      :Student(id, g, x, nm), s(t), dept(d), thesis(th) {}

It’s common for the constructor of the derived class to call the base class constructor. Now e.g., name can be private.

Page 21: STAT 598W: Lecture 12

The print() Methods

void Student::print() const { cout << name << ", " << studentID << ", " << (int)y << ", " << gpa;}

void GradStudent::print() const { Student::print(); cout << ", " << dept << ", " << (int)s << ", " << thesis;}

Page 22: STAT 598W: Lecture 12

Finally, a Driver Program

void main() { Student s(365, 2.53, sophomore, "Larry Fine"); Student* ps = &s; GradStudent gs(366, 2.03, graduate, "Curly Howard", ta, "Theatre", "Nyuk Nyuk Nyuk"); GradStudent* pgs; ps->print(); cout << endl; ps = pgs = &gs; // implicit conversion of gradStudent* to student* ps->print(); // student::print() cout << endl; pgs->print(); // gradStudent::print() cout << endl;}

Page 23: STAT 598W: Lecture 12

Here is the Output

Larry Fine, 365, 1, 2.53Curly Howard, 366, 4, 2.03Curly Howard, 366, 4, 2.03, Theatre, 0, Nyuk Nyuk Nyuk

Page 24: STAT 598W: Lecture 12

Static and Dynamic Typing Binding time: the time when an attribute or

meaning of a a program construct is determined. For instance, in strongly typed languages,

variable names are bound to variable types at compile time (e.g., int a). This leaves no room for variables to take on other types (c.f. variants in VBA).

Dynamically typed languages need some sort of run time system, for example to determine variable types and bind appropriate operators.

Essentially, the question is, are types associated with variables (names) or with values?

Page 25: STAT 598W: Lecture 12

Static and Dynamic Typing If x and y are declared ints, then the

compiler knows what to do with the expression x + y.

In OOP, there are additional problems. We saw before that pointers to base class types are valid pointers to subclass objects. This violates the spirit of “strong typing”.

Similarly, an object of a subclass type is a member of the superclass, so it is possible to make an assignment.

Page 26: STAT 598W: Lecture 12

Employees and Managers

void main() { Employee e; Manager m; e.setSalary(30000); e.print(); m.setSalary(40000); m.setLevel(4); m.print(); //m = e;              error: an e isn't an m e = m;             // Legal: an m is an e e.print(); // But, Employee::print() is called}

Page 27: STAT 598W: Lecture 12

The “Container Problem” If we view m as an Employee, can we

ever recover that m is really a Manager?

We would like to write abstract “container classes” like sets and lists, but in C++, heterogeneous classes are harder than homogeneous ones.

But if we use pointers to elements of collections, things can be done.

Page 28: STAT 598W: Lecture 12

Solving the Problem Given a pointer of type base *, how do

we know if it points to an object of type base or to some derived type?

Three possible solutions: Ensure that only objects of a single type are

pointed to. This insists on homogeneous containers.

Place a “type field” in the base class for functions to inspect.

Use “virtual functions”.

Page 29: STAT 598W: Lecture 12

Example of a Type Field

struct employee { // note this is a struct; everything public enum emp_type { M, E}; emp_type type; employee * next; char * name; // ...}

struct manager : employee { // also a struct employee * group; short level; // ... }

Page 30: STAT 598W: Lecture 12

Then, Define

void employee_print(employee * e) {   switch (e -> type) {   case E:     cout << e->name << '\t' << e->department << '\n';     break;   case M:     cout << e->name << '\t' << e->department << '\n';     manager * p = (manager*)e;     cout << "level " << p->level << '\n';     break; }

This is a really bad idea. Don’t do it!

Page 31: STAT 598W: Lecture 12

Printing the Employee List

A function to print employees might go like:

But what happens if a new subclass is defined? Go through all the code?

void f(employee * elist) { for (; elist; elist = elist->next) print_employee(elist); }

Page 32: STAT 598W: Lecture 12

Static and Dynamic Binding If a message is passed to a receiver,

which method responds to the message? On one hand, this is obvious: if the

receiver knows its type, then it will perform the method associated with that type, or look upwards.

On the other hand, there has to be a mechanism to “find an object's type,” and this may be expensive at run time.

Page 33: STAT 598W: Lecture 12

Static and Dynamic Binding Static binding if the declared object type

determines the method to use. Dynamic binding if the actual type (at

run time) determines the method to use. C++ “prefers” to use static binding,

since it imposes no additional overhead. C++ can be forced to use dynamic

binding if necessary (the programmer has to ask for it explicitly).

Page 34: STAT 598W: Lecture 12

Virtual Functions

For dynamic binding, C++ gives us virtual functions.

The virtual keyword says that a function may be overridden in a subclass, and that the type of the object receiving a message should determine which method (function) to use.

Page 35: STAT 598W: Lecture 12

Virtual Function Example

class Base {public: int i;   virtual void print_i() { cout << i << " inside Base\n"; } };

class Derived : public Base { public:   virtual void print_i() {        cout << i << " inside Derived\n"; } };

This is new

Page 36: STAT 598W: Lecture 12

Virtual Function Example

void main() { Base b; Base* pb = &b; Derived d; b.i = 1 d.i = 2; pb->print_i(); pb = &d; pb->print_i(); }

This yields:

1 inside Base 2 inside Derived

Even though pb was declared a pointer to type Base, it may point to a Derived object. When it does, Derived's member function is chosen.

Page 37: STAT 598W: Lecture 12

Abstract Classes In the employee example, the base

class “makes sense”, that is, we can conceive of objects of that type.

Sometimes this is not the case. class Shape {public: virtual void rotate(int) { error(“Shape:rotate"); } virtual void draw() { error(“Shape::draw"); }};

Page 38: STAT 598W: Lecture 12

Pure Virtual Functions Making a shape of this unspecified kind

is rather pointless, since every operation on this object results in an error.

We can get the compiler to help us keep track by making Shape an abstract class.

This is done by defining one or more of its member functions as pure virtual.

Page 39: STAT 598W: Lecture 12

Pure Virtual Functions

Now no objects of type Shape may be created.

An abstract class can only be used as a base class for derived types.

class Shape { // ... public:     virtual void rotate(int) = 0;     virtual void draw() = 0;     // ... };

Page 40: STAT 598W: Lecture 12

Pure Virtual Functions

Page 41: STAT 598W: Lecture 12

Let’s Use This Stuff!

Some classes to represent arithmetic expressions: Term (abstract) Constant : public Term BinaryOp : public Term (abstract) Plus : public BinaryOp

Page 42: STAT 598W: Lecture 12

Expression Trees

+

+

2.21.1

3.3

Term

Variablename

Constantvalue

Expressionoperator

Binary Unary

operands This is the “Composite”design pattern

Page 43: STAT 598W: Lecture 12

Starting the Inheritance Hierarchy

#include <iostream>#include <sstream>#include <string>using namespace std;

class Term {public: Term() {} virtual ~Term() {} virtual string symbolicEval() = 0; virtual double numericalEval() = 0;};

Our basic abstract class.It has two pure virtualmethods, and a virtualdestructor.

symbolicEval() writesan expression like((1.1 + 2.2) + 3.3)

numericalEval() evaluatesit: 6.6

Page 44: STAT 598W: Lecture 12

The Constant Class

class Constant : public Term { double value;public: Constant() { value = 0; } Constant(double v) { value = v; } virtual ~Constant() {} virtual string symbolicEval() { ostringstream oss; oss << value; return oss.str(); } virtual double numericalEval() { return value; }};

This class is no longerabstract, since thepure virtuals areoverridden.

symbolicEval() usesan ostringstreamobject that allows“<<-ing” into astring.

Page 45: STAT 598W: Lecture 12

The BinaryOp Class

class BinaryOp : public Term {public: virtual ~BinaryOp() { if (lChild) delete lChild; if (rChild) delete rChild; }protected: Term * lChild, * rChild; BinaryOp(Term * l, Term * r) { lChild = l; rChild = r; }};

This is the parent class forthe binary arithmeticoperators. It centralizescommon constructionand destruction activities.

Note that this class is stillabstract.

The Expression class in theUML diagram is conceptuallynice, but not needed inhere.

Page 46: STAT 598W: Lecture 12

Plus: A Typical Binary Operator

class Plus : public BinaryOp {public: Plus(Term * l, Term * r) : BinaryOp(l, r) {} virtual ~Plus() {} virtual string symbolicEval() { ostringstream oss; oss << "(" << lChild->symbolicEval(); oss << " + "; oss << rChild->symbolicEval() << ")"; return oss.str(); } virtual double numericalEval() { return (lChild->numericalEval() + rChild->numericalEval()); }};

Page 47: STAT 598W: Lecture 12

A Simple Driver

void main() { Constant * c1 = new Constant(1.1); Constant * c2 = new Constant(2.2); Constant * c3 = new Constant(3.3); Plus * p1 = new Plus(c1, c2); Plus * p2 = new Plus(p1, c3); cout << p2->symbolicEval() << " = "; cout << p2->numericalEval() << endl; delete p2;}

Note that all Terms are created with new, and the tree is “heldtogether” with pointers. Pay particular attention to the waydelete works. Term’s destructor must be virtual for this to work!

Page 48: STAT 598W: Lecture 12

More Advanced Stuff

Page 49: STAT 598W: Lecture 12

Beware Implicit Conversions

class Base { public:   virtual void foo(int) { cout << “Base::foo(int)” << endl;}   virtual void foo(double) {cout << “Base::foo(double)” << endl;}   //... };

class Derived : public Base { public:   virtual void foo(int) {cout << “Derived::foo(int)” << endl;}   //... };

Page 50: STAT 598W: Lecture 12

Beware Implicit Conversions

void main() { Derived d; Base b, *pb = &d;

b.foo(9); // selects Base::foo(int) b.foo(9.5); // selects Base::foo(double) d.foo(9); // selects Derived::foo(int) d.foo(9.5); // selects Derived::foo(int) overriden pb->foo(9); // selects Derived::foo(int) pb->foo(9.5); // selects Base::foo(double) virtual func}

Page 51: STAT 598W: Lecture 12

Pure Virtual Functions

A pure virtual function which is not defined in a derived class remains a pure virtual function, so that the derived class is also an abstract class.

This allows us to build implementations in stages.

Page 52: STAT 598W: Lecture 12

Pure Virtual Functions

class X { public:     virtual void f() = 0;     virtual void g() = 0; }; X b;  // error: declaration of object of abstract class X class Y : public X {     void f();  // overrides X::f }; Y b;  // error: declaration of object of abstract class Y class Z : public Y {     void g();  // overrides X::g }; Z c;  // this is OK