polymorphism. abstract classes.. polymorphism essential feature of oop, with data abstraction and...
TRANSCRIPT
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.
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
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
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