Stat 598w lecture 12
This presentation is the property of its rightful owner.
Sponsored Links
1 / 52

STAT 598W: Lecture 12 PowerPoint PPT Presentation


  • 78 Views
  • Uploaded on
  • Presentation posted in: General

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).

Download Presentation

STAT 598W: Lecture 12

An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -

Presentation Transcript


Stat 598w lecture 12

STAT 598W: Lecture 12

Purdue University

More on Inheritance and Related Topics


Topics

Topics

  • Inheritance

  • Polymorphism

  • Virtual methods

  • Abstract classes


Inheritance

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...


Benefits of inheritance

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.


Benefits of inheritance1

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.


Benefits of inheritance2

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.


Costs of inheritance

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.


Implementation details

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


Managers are objects too

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 

// ...

};


A problem and a solution

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 Manageris anEmployee, so make it a subclass:

class Manager : public Employee { // public inheritance  

Employee* group;  

short level;  

// ...

};


Subclass advantages

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;

}


How does this work

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!)


Let s add some methods

Let's Add Some Methods

class Employee {  

string name;  

// ...

public:  

Employee* next;  

void print();  

// ...

};

class Manager : public Employee {  

// ...

public:  

void print();  

// ...

};


This would seem natural

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

}


The good solution

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

}


The not so good solution

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.


The inevitable student class

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 ;

};


Gradstudent s are student s

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


New concepts

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.


Code reuse with inheritance

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.


The print methods

The print() Methods

void Student::print() const {

cout << name << ", " << studentID << ", "

<< (int)y << ", " << gpa;

}

void GradStudent::print() const {

Student::print();

cout << ", " << dept << ", " << (int)s

<< ", " << thesis;

}


Finally a driver program

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;

}


Here is the output

Here is the Output

Larry Fine, 365, 1, 2.53

Curly Howard, 366, 4, 2.03

Curly Howard, 366, 4, 2.03, Theatre, 0, Nyuk Nyuk Nyuk


Static and dynamic typing

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?


Static and dynamic typing1

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.


Employees and managers

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

}


The container problem

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.


Solving the problem

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”.


Example of a type field

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;

// ...

}


Then define

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!


Printing the employee list

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);

}


Static and dynamic binding

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.


Static and dynamic binding1

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).


Virtual functions

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.


Virtual function example

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


Virtual function example1

Virtual Function Example

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.

void main() {

Base b;

Base* pb = &b;

Derived d;

b.i = 1

d.i = 2;

pb->print_i();

pb = &d;

pb->print_i();

}


Abstract classes

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");

}

};


Pure virtual functions

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.


Pure virtual functions1

Pure Virtual Functions

class Shape {

// ...

public:

    virtual void rotate(int) = 0;

    virtual void draw() = 0;

    // ...

};

  • Now no objects of type Shape may be created.

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


Pure virtual functions2

Pure Virtual Functions


Let s use this stuff

Let’s Use This Stuff!

  • Some classes to represent arithmetic expressions:

    • Term (abstract)

    • Constant : public Term

    • BinaryOp : public Term (abstract)

    • Plus : public BinaryOp


Expression trees

Expression Trees

operands

This is the “Composite”

design pattern

Term

Expression

Variable

Constant

+

operator

name

value

+

3.3

Binary

Unary

1.1

2.2


Starting the inheritance hierarchy

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 virtual

methods, and a virtual

destructor.

symbolicEval() writes

an expression like

((1.1 + 2.2) + 3.3)

numericalEval() evaluates

it: 6.6


The constant class

The Constant Class

This class is no longer

abstract, since the

pure virtuals are

overridden.

symbolicEval() uses

an ostringstream

object that allows

“<<-ing” into a

string.

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;

}

};


The binaryop class

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 for

the binary arithmetic

operators. It centralizes

common construction

and destruction activities.

Note that this class is still

abstract.

The Expression class in the

UML diagram is conceptually

nice, but not needed in

here.


Plus a typical binary operator

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());

}

};


A simple driver

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 “held

together” with pointers. Pay particular attention to the way

delete works. Term’s destructor must be virtual for this to work!


More advanced stuff

More Advanced Stuff


Beware implicit conversions

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;}

  //...

};


Beware implicit conversions1

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

}


Pure virtual functions3

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.


Pure virtual functions4

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


  • Login