polymorphism. abstract classes.. polymorphism essential feature of oop, with data abstraction and...

46
Polymorphism. Abstract classes.

Upload: eleanore-page

Post on 28-Dec-2015

239 views

Category:

Documents


0 download

TRANSCRIPT

Polymorphism. Abstract classes.

Polymorphism

• Essential feature of OOP, with data abstraction and inheritance

• Speration between interface and implementation – WHAT and HOW

• Improves code readability, more efficient program structure and development of extensible programs

Polymorphism

Definition 1: Polymorphism is the property of an entity to react differently depending on its state.

Definition 2: Polymorphism is the property that allows different entities to behave in different ways to the same action.

Definition 3: Polymorphism allows different objects to respond in different ways to the same message.

Shapes

Point.h

#ifndef POINT_H

#define POINT_H

#include <iostream>

using namespace std;

class Point{

int x,y;

public:

Point():x(0),y(0){}

Point(int x, int y):x(x),y(y){}

Point(const Point& p):x(p.x),y(p.y{}

Point(int x):x(x),y(0){}

int getX(){ return x; }

int getY(){ return y; }

~Point(){ }

};#endif

Point

-x: int-y: int

<<create>>+Point()<<create>>+Point(x: int, y: int)<<create>>+Point(p: Point)<<create>>+Point(x: int)+getX(): int+getY(): int<<destroy>>+Point()

Shape.h (1)#ifndef SHAPE_H#define SHAPE_H#include "Point.h"#include <cmath>#include <iostream>

using namespace std;

const double PI=3.14;

class Shape{ public: void draw() {cout<<"Shape::draw()"<<endl;} float area() {cout<<"Shape::area()"<<endl; return 0;} float perimeter() {cout<<"Shape::perimeter()"<<endl; return 0;} ~Shape(){}};

Shape

+draw(): void+area(): float+perimeter(): float<<destroy>>+Shape()

Shape.h (2)class Circle:public Shape{ private: Point c; float r;

public: Circle(Point c1, float r1):c(c1),r(r1){}

void draw(){cout<<"Circle::draw()";}

float area(){cout<<"Circle::area()"; return PI*r*r;}

float perimeter(){cout<<"Circle::perimeter()"; return 2*PI*r;} ~Circle(){} };

Shape

+draw(): void+area(): float+perimeter(): float<<destroy>>+Shape()

Circle

-r: float

<<create>>+Circle(c1: Point, r1: float)+draw(): void+area(): float+perimeter(): float<<destroy>>+Circle()

-c Point

-x: int-y: int

<<create>>+Point()<<create>>+Point(x: int, y: int)<<create>>+Point(p: Point)<<create>>+Point(x: int)+getX(): int+getY(): int<<destroy>>+Point()

Shape.h (3)class Square:public Shape{ private: Point leftUp, rightDown;

public: Square(Point p1, Point p2):leftUp(p1), rightDown(p2){}

void draw(){cout<<"Square::draw()"<<endl;}

float area(){cout<<"Square::area()"<<endl; float l= abs((float)(leftUp.getX()-rightDown.getX())); return l*l;}

float perimeter(){ cout<<"Square::perimeter()"<<endl; return 4*abs((float)(leftUp.getX()-rightDown.getX()));}

~Square(){} };#endif

Shape

+draw(): void+area(): float+perimeter(): float<<destroy>>+Shape()

Square

<<create>>+Square(p1: Point, p2: Point)+draw(): void+area(): float+perimeter(): float<<destroy>>+Square()

-leftUp

-rightDownPoint

-x: int-y: int

<<create>>+Point()<<create>>+Point(x: int, y: int)<<create>>+Point(p: Point)<<create>>+Point(x: int)+getX(): int+getY(): int<<destroy>>+Point()

Test.cpp#include "Shape.h"

void printArea(Shape& x){ cout<<x.area()<<endl;}

int main(){ Shape* x1 = new Circle(Point(1,2),5); Point p1(0,0), p2(1,1); Shape* x2 = new Square(p1, p2); printArea(*x1); printArea(*x2); return 0;

51

25π

1

Reality….

Why?

• Each shape must compute its area in a particular way, so there’ll be different versions of the method area in each subclass.

• The system must dynamically determine the code to be executed when the method area is called;

• The decision is based on the actual type of the object.

• This important feature (present in almost any OOL) is called dynamic binding (late binding, runtime binding).

• Dynamic binding means that the decision of which function body to execute is made at runtime, unlike static binding which means that the decision is made at compile time (before running) – forced by virtual methods

Virtual methods

• Declaration virtual <function_prototype>

– Virtual function declaration is performed only once in the base class – remains virtual in the derived classes

– If a method is declared virtual in the base class and is overriden in the derived classes, late binding is performed = method call depends on the actual type of the calling object

Virtual methods

• CONSTRUCTORS– May NOT be virtual (they create the VPTR)

• DESTRUCTORS– MAY be virtual, and sometimes they MUST be virtual

• Static methods may NOT be virtual

• Global friends may NOT be virtual

Object slicing

• Late binding is performed only when virtual methods are used in combination with addresses of the base class (pointers or references)

• When using objects sent by value - object slicing (when upcasting is performed)

• Sending by value/sending by reference

• It is recommended to use references, otherwise the derived classes instances are “bigger”

• When upcasting the object is “cut” such that only the common part with the base class objects is used

Persoana.h#ifndef PERSOANA_H#define PERSOANA_H#include <cstring>#include <cstdio>

#include <iostream>

using namespace std;

class Person{protected:

char* name;int age;

public:Persoana(char* n, int v){

name=new char[strlen(n)+1];strcpy(name,n);age=v;}

Person(const Person& p){

nume=new char[strlen(p.name)+1];

strcpy(name,p.nume);

age=p.age; }

virtual char* toString(){char* aux=new char[25];sprintf(aux,"%s %d", name, varsta);return aux;}

~Persoana(){if (name) delete [] name;}

};

class Student:public Person{char* faculty;

public:Student(char* n, int v, char* f):Persoana(n,v){

faculty=new char[strlen(f)+1];strcpy(faculty,f);

}

Student(const Student& s):Person(s){ faculty=new char[strlen(s.faculty)+1];

strcpy(faculty,s.faculty); }

char* toString(){//still virtual char* aux = new char[30]; sprintf(aux,"%s %d %s",name, age,faculty); return aux;}

~Student(){if (faculty)

delete [] faculty;}

};#endif

Test.cpp#include "Persoana.h"#include <iostream>using namespace std;

void print (Person p){cout<<p.toString()<<endl;

}

int main(){Persoana* p= new Student("Ioana",21,"Informatica");print(*p);delete(p);system("pause");return 0;};

#include "Persoana.h"#include <iostream>using namespace std;

void print (Person& p){cout<<p.toString()<<endl;

}

int main(){Person* p= new Student("Ioana",21,"Informatica");print(*p);delete(p);return 0;};

faculty???

Object slicing

Object slicing

Ioana 21Person::~PersonPerson::~Person

Ioana 21 InformaticaPerson::~Person

Virtual destructors#ifndef PERSOANA_H#define PERSOANA_H#include <cstring>#include <cstdio>

class Person{protected:

char* name;int age;

public:Person(char* n, int v){

name=new char[strlen(n)+1];strcpy(name,n);age=v;

}

virtual char* toString(){char* aux=new char[25];sprintf(aux,"%s %d", name, age);return aux;

}

virtual ~Persoana(){if (name)

delete [] name;}

};

Ioana 21 InformaticaStudent::~StudentPerson::~Person

The mechanism

Object

Virtual Method

Dispatch Table

(VTABLE)

virtual_method1

virtual_method2

. . .

VPTR

The mechanism· the object itself must store some information about its actual type

· Based on this type the call is dispatched to the appropriate implementation of the method. In fact, the object may store a pointer to a dispatch table through which virtual methods are called.

· Every class that has at least one virtual method (a polymorphic class) has a hidden member that is a pointer to a virtual table (VPTR).

· The virtual table contains the addresses of the virtual member functions for that particular class.

· When a call is made through a pointer or reference to such object, the compiler generates code that dereferences the pointer to the object’s virtual table and makes an indirect call using the address of a member function stored in the table.

The mechanism

Circle

vptrCircle

vptr

Squarevptr

Shape*

&Circle::draw

&Circle::perimeter

&Circle::area

&Square::draw

&Square::perimeter

&Square::area

&Circle::draw

&Circle::perimeter

&Circle::area

Explanations• An object of a derived class that has overridden the implementation of the virtual

method has its pointer pointed to a different virtual table. In that table the entry corresponding to that particular method contains the address of the overridden member function.

• Based on the type of the pointer, the compiler has enough information to decide whether to generate an indirect call through a VTABLE, or a direct call or inline expansion.

• If the pointer is declared to point to a class that has virtual functions, and the call is made to one of these functions, an indirect call is automatically generated. Otherwise a direct call or inline expansion is done.

• In any case, the compiler must first see the declaration of the base class (presumably in one of the header files) to know which functions are virtual and which are not.

• Shape – methods should be virtual…

Shape.h (version 2)#ifndef SHAPE_H#define SHAPE_H#include <cmath>#include <iostream>#include "Point.h"using namespace std;

const double PI=3.14;class Shape{ public: virtual void draw() {cout<<"Shape::draw()"<<endl;} virtual float area() {cout<<"Shape::area()"<<endl; return 0;} virtual float perimeter() {cout<<"Shape::perimeter()"<<endl;return 0;} virtual ~Shape(){}};

class Circle:public Shape{ private: Point c; float r; public: Circle(Point c1, float r1):c(c1),r(r1){} void draw(){cout<<"Circle::draw()";} float area(){cout<<"Circle::area()"; return PI*r*r;} float perimeter(){cout<<"Circle::perimeter()"; return

2*PI*r;} ~Circle(){} }; class Square:public Shape{ private: Point leftUp, rightDown; public: Square(Point p1, Point p2):leftUp(p1), rightDown(p2)

{} void draw(){cout<<"Square::draw()"<<endl;} float area(){cout<<"Square::area()"<<endl; float l= abs(leftUp.getX()-rightDown.getX()); return l*l;} float perimeter(){cout<<"Square::perimeter()"<<endl; return 4*abs(leftUp.getX()-rightDown.getX()); } ~Square(){} };#endif`

Test.cpp

#include "Shape.h"

void printArea(Shape& x){

cout<<x.area()<<endl;

}

int main(){

Shape* x1 = newCircle(Point(1,2),5);

Point p1(0,0), p2(1,1);

Shape* x2 = new Square(p1, p2);

printArea(*x1);

printArea(*x2);

return 0;

}

51

25π

1

Shape.h#ifndef SHAPE_H#define SHAPE_H#include "Point.h"#include <cmath>#include <iostream>

using namespace std;

const double PI=3.14;

class Shape{ public: virtual void draw() {cout<<"Shape::draw()"<<endl;} virtual float area() {cout<<"Shape::area()"<<endl; return 0;} virtual float perimeter() {cout<<"Shape::perimeter()"<<endl; return 0;} virtual ~Shape(){}};

“dummy code”

Abstract classes

• An abstract class serves as a base class for a collection of related derived classes;

• It provides:– a common public interface (or pure virtual member

functions)– any shared representation– any shared member functions

• an abstract class does NOT have instances

Pure virtual methods

• pure virtual function (at least one!):virtual <return-type> <name> (<parameters>) = 0;

• The mechanism: you tell the compiler to reserve a slot for a function in the VTABLE, but not to put an address in that particular slot. Even if only one function in a class is declared as pure virtual, the VTABLE is incomplete .

• pure abstract class = class has nothing but pure virtual functions

• pure abstract class = interface

Concrete classes that extend an abstract class

• A concrete class is derived from an abstract class, inheriting its public interface

• a concrete class is expected to have instances

• override inherited abstract functions to provide concrete implementation specific to its representation (otherwise it will also be abstract classes)

• Shape – interface– describes the common behavior of all shapes

Shape.h (version 3)#ifndef SHAPE_H#define SHAPE_H#include <cmath>#include <iostream>#include "Point.h"using namespace std;

const double PI=3.14;class Shape{ public: virtual void draw()=0; virtual float area()=0; virtual float perimeter()=0; virtual ~Shape()=0;};

Shape::~Shape(){} Pure virtual destructor– must have an empty body

Test.cpp#include "Shape.h"

int main(){

Shape* s[3];

Point center(2,3);s[0]=new Circle(center,4);

Point lU(2,2), rD(5,5);s[1]= new Square(lU,rD);

s[2]=new Circle(lU,7);

for(int i=0;i<3;i++) cout<<s[i]->area()<<endl;

for(int i=0;i<3;i++) delete s[i];

return 0;}

s

UML – abstract classes

Shape

+draw(): void+area(): float+perimeter(): float<<destroy>>+Shape()

Italics

Polymorphism advantages

• Polymorphic functions

void printArea(Shape& s){

cout<<s.area()<<“ “;

}

• Polymorphic abstract data types– containers of generic elements (WITHOUT void* !!!)

Vector of generic elements

Array

-n: int

<<create>>+Array(n1: int)+add(p: PElem): void+getElem(pos: int): PElem+setElem(p: PElem, pos: int): void+length(): int+remove(pos: int): void<<destroy>>+Array()

TElem

+equals(: PElem): int+toString(): char<<destroy>>+TElem()

Integer

-i: int

<<create>>+Integer(n: int)+toString(): char+equals(p: PElem): int<<destroy>>+Integer()

Person

-name: char

<<create>>+Person(n: char)+toString(): char+equals(p: PElem): int<<destroy>>+Person()

PElem<<CppTypedef>>

<<CppSynonym>>-elem

Vector of generic arrays

Realisation (interface implementation)

Aggregation

Element.h

#ifndef ELEM_H

#define ELEM_H

class TElem;

typedef TElem* PElem;

class TElem{

public:

virtual int equals(PElem)=0;

virtual char* toString()=0;

virtual ~TElem(){};

};

#endif

PElem<<CppTypedef>>

<<CppSynonym>> TElem

+equals(: PElem): int+toString(): char<<destroy>>+TElem()

Integer.h#ifndef INTEGER_H#define INTEGER_H#include <cstdio>#include <cstring>#include "Element.h"using namespace std;

class Integer:public TElem{ private: int i; public: Integer(int n=0):i(n){} char* toString(){ char* buf=new char[3]; sprintf(buf,"%d",i); return buf;} int equals(PElem p){ return i==((Integer*)p)->i;} ~Integer(){} };#endif

Integer

-i: int

<<create>>+Integer(n: int)+toString(): char+equals(p: PElem): int<<destroy>>+Integer()

TElem

+equals(: PElem): int+toString(): char<<destroy>>+TElem()

PElem<<CppTypedef>>

<<CppSynonym>>

Person.h#ifndef PERSON_H#define PERSON_H

#include "Element.h"

class Person:public TElem{ char* name; public: Person(char* n){ name=new char[strlen(n)+1]; strcpy(name,n); } char* toString(){return name;} int equals(PElem p){ return strcmp(name, ((Person*)p)->name)==0; } ~Person(){ delete [] name;}};

#endif

TElem

+equals(: PElem): int+toString(): char<<destroy>>+TElem()

Person

-name: char

<<create>>+Person(n: char)+toString(): char+equals(p: PElem): int<<destroy>>+Person()

Array.h#ifndef ARRAY_H#define ARRAY_H#include "Element.h"

class Array{ PElem* elem; int n; public: Array(int n1){elem = new PElem[n1]; n=0;}

void add(PElem p){elem[n++]=p;}

PElem getElem(int pos){return elem[pos];}

void setElem(PElem p, int pos){elem[pos]=p;}

int length(){return n;}

void remove(int pos){elem[pos]=elem[--n];}

~Array(){for(int i=0;i<n;i++) delete elem[i]; delete [] elem;} };#endif

PElem<<CppTypedef>>

<<CppSynonym>> TElem

+equals(: PElem): int+toString(): char<<destroy>>+TElem()

-elemArray

-n: int

<<create>>+Array(n1: int)+add(p: PElem): void+getElem(pos: int): PElem+setElem(p: PElem, pos: int): void+length(): int+remove(pos: int): void<<destroy>>+Array()

TestArray.cpp#include "Integer.h"#include "Person.h"#include "Array.h"#include <iostream>using namespace std;

void print(Array& a){ for(int i=0;i<a.length();i++) cout<<a.getElem(i)->toString()<<" "; cout<<endl; }

int main(){Array a(4);a.add(new Integer(3));a.add(new Integer(4));a.add(new Integer(5));a.add(new Person("Adriana"));print(a);return 0;}

Multiple inheritance

Top

#x: int

<<create>>+Top(x1: int)

Left

#y: int

<<create>>+Left(x1: int, y1: int)

Right

#z: int

<<create>>+Right(x1: int, z1: int)

Bottom

-w: int

<<create>>+Bottom(x1: int, y1: int, z1: int, w1: int)<<CppFriend>>+operator<<(os: ostream, b: Bottom): ostream

Diamond inheritance

Diamond inheritance#ifndef DI_H#define DI_H…class Top{protected: int x;public:Top(int x1){x=x1;} };

class Left:public Top{protected: int y; public: Left (int x1, int y1):Top(x1){y=y1;} };

class Right:public Top{protected: int z; public: Right(int x1, int z1):Top(x1){z=z1;} };

class Bottom: public Left, public Right{ int w; public:

Bottom(int x1, int y1, int z1, int w1): /*Top(x1),*/Left(x1, y1), Right(x1, z1){w=w1;}//Top is not a base class of Bottom

friend ostream& operator<<(ostream& os, Bottom& b){ return os<<" "<<b.y<<" "<<b.z<<" "<<b.w<<endl;//x …ambiguous };#endif

Diamond inheritance

#include "DiamondInheritance.h"

int main(){

Bottom b(1,2,3,4);

cout<<sizeof(b)<<endl;

cout<<b;

return 0;

}

Top

#x: int

<<create>>-Top(x1: int)<<destroy>>-Top()

Left

#y: int

<<create>>-Left(x1: int, y1: int)

<<virtual>>

Right

#z: int

<<create>>-Right(x1: int, z1: int)

<<virtual>>

Bottom

-w: int

<<create>>-Bottom(x1: int, y1: int, z1: int, w1: int)<<CppFriend>>+<<(os: ostream, b: Bottom): ostream

True diamond inheritance

Virtual inheritance#ifndef DI_H

#define DI_H

#include <iostream>

using namespace std;

class Top{

protected:

int x;

public:

Top(int x1){x=x1;}

virtual ~Top(){}//all base classes must have virtual destructors

};

class Left: virtual public Top{

protected:

int y;

public:

Left (int x1, int y1):Top(x1){y=y1;}

};

class Right: virtual public Top{protected: int z; public: Right(int x1, int z1):Top(x1){z=z1;} }; class Bottom: public Left, public Right{ int w; public: // the last derived class will initialize the base

class – othrwise - ambiguities(Left or Right?) Bottom(int x1, int y1, int z1, int w1): Top(x1),

Left(0, y1), Right(0, z1){w=w1;} friend ostream& operator<<(ostream& os,

Bottom& b){ return os<<b.x<<" "<<b.y<<" "<<b.z<<"

"<<b.w<<endl;//x poate fi tiparit...virtual } };#endif

True diamond inheritance

#include "DiamondInheritance.h"

int main(){

Bottom b(1,2,3,4);cout<<sizeof(b)<<endl;cout<<b;return 0;}

True diamond inheritance

• When b:Bottom is instantiated, the b object will look as follows:

• The subobjects Left and Right contain each of them a pointer to the same shared subobject Top

• When using multiple inheritance, an instance of the derived class behaves as it has multiple VPTRs, one for each direct base class

Left

Right

Bottom

Top

Conclusions

• Multiple inheritance – NOT recommended – – Java – simple inheritance + implementation of multiple interfaces

• Virtual methods mechanism – why not the DEFAULT behavior? –Efficiency