Polimorfism Clase abstracte Polimorfismul A treia trasatura esentiala
Polimorfism. Clase abstracte.
Polimorfismul • A treia trasatura esentiala in POO, dupa abstractizarea datelor si mostenire • O alta dimensiune a separarii intre interfata si implementare, intre CE si CUM • Determina o mai buna lizibilitate a codului, organizare mai eficienta si dezvoltarea de programe extensibile
Polimorfism Definitia 1: Polimorfismul este proprietatea unei entitati de a reactiona diferit in functie de starea sa. Definitia 2: Polimorfismul este proprietatea care permite unor entitati diferite sa se comporte diferit la aceeasi actiune. Definitia 3: Polimorfismul permite unor obiecte diferite sa raspunda diferit la acelasi mesaj.
Figuri geometrice
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 get. X(){ return x; } int get. Y(){ return y; } ~Point(){ } }; #endif
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. h (2) class Circle: public Shape{ private: Point c; float r; public: Circle(Point c 1, float r 1): c(c 1), r(r 1){} 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. h (3) class Square: public Shape{ private: Point left. Up, right. Down; public: Square(Point p 1, Point p 2): left. Up(p 1), right. Down(p 2){} void draw(){cout<<"Square: : draw()"<<endl; } float area(){cout<<"Square: : area()"<<endl; float l= abs((float)(left. Up. get. X()-right. Down. get. X())); return l*l; } float perimeter(){ cout<<"Square: : perimeter()"<<endl; return 4*abs((float)(left. Up. get. X()-right. Down. get. X())); } ~Square(){} }; #endif
Test. cpp #include "Shape. h" void print. Area(Shape& x){ cout<<x. area()<<endl; } 25π 5 1 int main(){ Shape* x 1 = new Circle(Point(1, 2), 5); Point p 1(0, 0), p 2(1, 1); Shape* x 2 = new Square(p 1, p 2); print. Area(*x 1); print. Area(*x 2); return 0; 1
Realitatea….
Motivul? • Legarea (binding) –conectarea unui apel al unei functii la corpul functiei • Legarea timpurie (early binding) – stabilirea la momentul compilarii a metodelor care vor fi apelate (compilatoare C) • Solutia? • Legarea intarziata (late binding, dynamic binding, runtime binding) – stabilirea doar la momentul executiei a metodelor care vor fi apelate – functii virtuale • Compilatorul nu va sti tipul actual al obiectului, dar insereaza cod care determina corpul corect al functiei
Functii virtuale • Declaratia virtual <antet_functie> – Declaratia unei functii virtuale se face o singura data, in clasa de baza, ea ramanand virtuala in clasele derivate – Daca o functie este declarata virtuala in clasa de baza si este suprascrisa intr-o clasa derivata, are loc legarea intarziata = apelul metodei corespunzatoare depinde de tipul actual al obiectului apelant
Functii virtuale • CONSTRUCTORII – NU pot fi virtuali (ei instaleaza VPTR) • DESTRUCTORII – POT fi virtuali si in unele situatii chiar TREBUIE sa fie virtuali • Legarea intarziata are loc numai cand se folosesc functii virtuale si adrese ale clasei de baza (referinte sau pointeri) • La folosirea obiectelor transmise prin valoare - object slicing la upcasting
Object slicing • Diferenta intre transmiterea prin valoare/ transmiterea prin adresa • Este recomandata transmiterea de adrese (au aceesi dimensiune)…altfel, de obicei obiectele derivate sunt “mai mari” decat obiectele clasei de baza • Cand se face upcast la un obiect al clasei de baza – obiectul este “decupat” astfel incat ramane doar partea comuna cu obiectele clasei de baza
Persoana. h #ifndef PERSOANA_H #define PERSOANA_H #include <cstring> #include <cstdio> #include <iostream> using namespace std; class Persoana{ protected: char* nume; int varsta; public: Persoana(char* n, int v){ nume=new char[strlen(n)+1]; strcpy(nume, n); varsta=v; } Persoana(const Persoana& p){ nume=new char[strlen(p. nume)+1]; strcpy(nume, p. nume); varsta=p. varsta; } virtual char* to. String(){ char* aux=new char[25]; sprintf(aux, "%s %d", nume, varsta); return aux; } ~Persoana(){ if (nume) delete [] nume; } }; class Student: public Persoana{ char* facultate; public: Student(char* n, int v, char* f): Persoana(n, v){ facultate=new char[strlen(f)+1]; strcpy(facultate, f); } Student(const Student& s): Persoana(s){ facultate=new char[strlen(s. facultate)+1]; strcpy(facultate, s. facultate); } char* to. String(){ char* aux = new char[30]; sprintf(aux, "%s %d %s", nume, varsta, facultate); return aux; } ~Student(){ if (facultate) delete [] facultate; } }; #endif
Test. cpp #include "Persoana. h" #include <iostream> using namespace std; Object slicing #include "Persoana. h" #include <iostream> using namespace std; void print (Persoana p){ cout<<p. to. String()<<endl; } void print (Persoana& p){ cout<<p. to. String()<<endl; } int main(){ Persoana* p= new Student("Ioana", 21, "Informatica"); print(*p); delete(p); system("pause"); return 0; }; int main(){ Persoana* p= new Student("Ioana", 21, "Informatica"); print(*p); delete(p); return 0; }; Object slicing facultate? ? ?
Destructor virtual #ifndef PERSOANA_H #define PERSOANA_H #include <cstring> #include <cstdio> class Persoana{ protected: char* nume; int varsta; public: Persoana(char* n, int v){ nume=new char[strlen(n)+1]; strcpy(nume, n); varsta=v; } virtual char* to. String(){ char* aux=new char[25]; sprintf(aux, "%s %d", nume, varsta); return aux; } virtual ~Persoana(){ if (nume) delete [] nume; } };
Mecanismul Obiect VPTR virtual_method 1 Virtual Method virtual_method 2 Dispatch Table . . . (VTABLE)
Mecanismul • Fiecare clasa care cel putin o metoda virtuala are o data membra ascunsa – pointer la tabela functiilor virtuale – contine adresele functiilor virtuale pentru clasa corespunzatoare • Cand se face un apel folosind un pointer sau o referinta la un astfel de obiect, compilatorul genereaza cod care dereferentiaza pointerul spre tabela de functii virtuale a clasei si face un apel indirect folosind adresa functiei membre a clasei din tabela functtilor virtuale &Circle: : draw Shape* &Circle: : perimeter Circle vptr &Circle: : area &Square: : draw Square vptr &Square: : perimeter &Square: : area Circle vptr &Circle: : draw &Circle: : perimeter &Circle: : area
• Un obiect al unei clase derivate care a suprascris (override) implementarea unei metode virtuale din clasa de baza, are pointerul VPTR indicand spre o VTABLE diferita de a clasei de baza, unde inregistrarea corespunzatoare acelei metode contine adresa functiei suprascrise • Shape – metodele ar trebui sa fie virtuale…
Shape. h (versiunea 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; // const double PI=3. 14; public: Circle(Point c 1, float r 1): c(c 1), r(r 1){} 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 left. Up, right. Down; public: Square(Point p 1, Point p 2): left. Up(p 1), right. Down(p 2){} void draw(){cout<<"Square: : draw()"<<endl; } float area(){cout<<"Square: : area()"<<endl; float l= abs(left. Up. get. X()-right. Down. get. X()); return l*l; } float perimeter(){cout<<"Square: : perimeter()"<<endl; return 4*abs(left. Up. get. X()right. Down. get. X()); } ~Square(){} }; #endif
Test. cpp #include "Shape. h" void print. Area(Shape& x){ cout<<x. area()<<endl; } int main(){ Shape* x 1 = new. Circle(Point(1, 2), 5); Point p 1(0, 0), p 2(1, 1); Shape* x 2 = new Square(p 1, p 2); print. Area(*x 1); print. Area(*x 2); return 0; } 25π 5 1 1
Shape. h #ifndef SHAPE_H #define SHAPE_H #include "Point. h" #include <cmath> #include <iostream> using namespace std; “dummy code” 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(){} };
Clase abstracte • O clasa abstracta foloseste ca baza pentru o colectie de clase derivate - prezinta o interfata pentru clasele derivate • Ea furnizeaza: – O interfata comuna publica (functii virtuale pure) – Oride reprezentare partajata (atribut comun) – Orice functie membra comuna (comportament comun) • O clasa abstracta nu are instante!!!! (dar se pot defini pointeri sau referinte la astfel de clase) • Are functii virtuale pure
Functii virtuale pure • Declarare: virtual <tip_returnat> <nume>(<lpf>)=0; • Mecanism: – Compilatorul rezerva un slot in VTABLE, dar nu pune nici o adresa in acel slot – VTABLE nu va fi completa chiar daca exista o singura functie virtuala pura • Clasa abstracta pura – are numai functii virtuale pure = interfata
Clase concrete care mostenesc clase abstracte • O clasa concreta care mosteneste interfata publica a clasei abstracte • O clasa concreta se doreste sa aiba instante • Suprascrie metodele abstracte (pure) pentru a furniza implementari concrete specific reprezentarii proprii, altfel devine la randul sau o clasa abstracta • Shape – interfata – descrie comportamentul comun tuturor figurilor geometrice
Shape. h (versiunea 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(){} Destructor virtual pur – are nevoie intotdeauna de corp vid
Test. cpp #include "Shape. h" int main(){ Shape* s[3]; Point center(2, 3); s[0]=new Circle(center, 4); Point l. U(2, 2), r. D(5, 5); s[1]= new Square(l. U, r. D); s[2]=new Circle(l. U, 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
Reprezentarea UML – clase abstracte Litere italic
Avantajele polimorfimsului • Functii polimorfice void print. Area(Shape& s){ cout<<s. area()<<“ “; } • Structuri de date polimorfice – containere cu diferite tipuri de elemente (fara void* !!!)
Vector cu elemente generice
Element. h #ifndef ELEM_H #define ELEM_H class TElem; typedef TElem* PElem; class TElem{ public: virtual int equals(PElem)=0; virtual char* to. String()=0; virtual ~TElem(){}; }; #endif
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* to. String(){ char* buf=new char[3]; sprintf(buf, "%d", i); return buf; } int equals(PElem p){ return i==((Integer*)p)->i; } ~Integer(){} }; #endif
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* to. String(){return name; } int equals(PElem p){ return strcmp(name, ((Person*)p)>name)==0; } ~Person(){ delete [] name; } }; #endif
Array. h #ifndef ARRAY_H #define ARRAY_H #include "Element. h" class Array{ PElem* elem; int n; public: Array(int n 1){elem = new PElem[n 1]; n=0; } void add(PElem p){elem[n++]=p; } PElem get. Elem(int pos){return elem[pos]; } void set. Elem(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
Test. Array. 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. get. Elem(i)->to. String()<<" "; 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; }
Mostenire multipla Diamond inheritance
Diamond inheritance #ifndef DI_H #define DI_H … class Top{ protected: int x; public: Top(int x 1){x=x 1; } }; class Left: public Top{ protected: int y; public: Left (int x 1, int y 1): Top(x 1){y=y 1; } }; class Right: public Top{ protected: int z; public: Right(int x 1, int z 1): Top(x 1){z=z 1; } }; class Bottom: public Left, public Right{ int w; public: Bottom(int x 1, int y 1, int z 1, int w 1): /*Top(x 1), */Left(x 1, y 1), Right(x 1, z 1){w=w 1; }//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 "Diamond. Inheritance. h" int main(){ Bottom b(1, 2, 3, 4); cout<<sizeof(b)<<endl; cout<<b; return 0; }
True diamond inheritance
Mostenire virtuala #ifndef DI_H #define DI_H #include <iostream> using namespace std; class Top{ protected: int x; public: Top(int x 1){x=x 1; } virtual ~Top(){}//all base classes must have virtual destructors }; class Left: virtual public Top{ protected: int y; public: Left (int x 1, int y 1): Top(x 1){y=y 1; } }; class Right: virtual public Top{ protected: int z; public: Right(int x 1, int z 1): Top(x 1){z=z 1; } }; class Bottom: public Left, public Right{ int w; public: //ultima dintre clasele derivate va trebui sa initializeze clasa de baza, altfel vor exista ambiguitati (Left sau Right? ) Bottom(int x 1, int y 1, int z 1, int w 1): Top(x 1), Left(0, y 1), Right(0, z 1){w=w 1; } 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 "Diamond. Inheritance. h" int main(){ Bottom b(1, 2, 3, 4); cout<<sizeof(b)<<endl; cout<<b; return 0; }
True diamond inheritance • Cand b: Bottom este instantiat, obiectul b va aparea astfel: Left Right Bottom Top • Subobiectele Left si Right are fiecare cate un pointer la acelasi subobiect partajat Top • Cand apare mostenirea multipla, un obiect al clasei derivate se comporta ca si cum ar avea mai multi VPTR, cate unul pentru fiecare clasa de baza directa
Concluzii • Mostenirea multipla – nerecomandata – – solutia Java – mostenire simpla si implementare de interfete multiple • Mecanismul functiilor virtuale – de ce nu implicit? – motive de eficienta
- Slides: 44