In cadrul acestui laborator, vom discuta despre mostenirea multipla si ambiguitatile care pot aparea o data cu aceasta, despre clasele si functiile abstracte si despre interfete, cu ajutorul carora vom reusi sa aprofundam cunostintele legate de derivarea claselor si suprascrierea metodelor acestora.
Ca referinte externe, recomandam urmatoarele sectiuni din Absolute C++:
Spre deosebire de multe alte limbaje de programare orientate-obiect, C++ permite mostenirea multipla (o clasa poate fi derivata din mai multe clase de baza).
class C:public A, public B
Desi mostenirea multipla pare a fi un procedeu util, aceasta poate duce la multe ambiguitati, cea mai mare dintre acestea fiind Problema Diamantului (numita si “death diamond”). Sa consideram urmatoarea ierarhie de clase:
In acest exemplu, avem o clasa de baza numita LivingThing avand metoda breathe(). Clasele Animal si Reptile mostenesc clasa LivingThing si suprascriu in moduri diferite metoda breathe(). Clasa Snake mosteneste ambele clase, Animal si Reptile, insa nu suprascrie metoda breathe().
class Animal: virtual public LivingThing class Reptile: virtual public LivingThing
Clasele de baza virtuale sunt utile in cadrul mostenirii multiple cand o serie de clase sunt derivate din aceeasi clasa de baza, iar aceasta urmeaza a fi clase parinte pentru o alta clasa.
Efectul acestei mosteniri virtuale nu este sesizat in clasele Animal si Reptile, ci se observa in urmatorul nivel de derivare, clasa Snake.
O metoda virtuala se numeste abstracta (pura) daca nu are implementare:
virtual tip_returnat metoda(lista parametrii) = 0;
Uneori implementarea tuturor functiilor dintr-o clasa de baza nu poate fi realizata, deoarece nu stim cu certitudine implementarea acestora. Sa presupunem ca avem o clasa de baza Shape. Nu putem implementa metoda draw() in Shape, insa stim cu siguranta ca fiecare clasa derivata o sa aiba aceasta metoda implementata.
#include <iostream> using namespace std; //Clasa abstracta class Shape { //Membrii clasei protected: int id; public: //Functie abstracta virtual void draw() = 0; }; //Clasa care mosteneste Shape si implementeaza draw class Circle: public Shape { public: void draw() { cout << "DRAW CIRCLE!" << endl; } }; int main () { Circle c; c.draw(); //Se va afisa DRAW CIRCLE! return 0; }
Interfetele si clasele abstracte sunt foarte importante pentru dezvoltarea ierarhiilor de clase – acestea reprezinta o schita a ceea ce trebuie implementat/suprascris in clasele derivate.
Clasele derivate din una sau mai multe interfete sunt obligate ca, eventual, sa furnizeze implementari pentru toate metodele abstracte.
Ca exemplu, consideram urmatoarea interfata Shape, cu metodele abstracte perimeter() si area(). Avand in vedere faptul ca, pentru fiecare figura geometrica in parte exista alte formule pentru calculul perimetrului si al ariei, fiecare clasa derivata va avea propria implementare pentru cele doua metode.
//Interfata class Shape { //Nu are atribute, toate metodele sunt abstracte public: virtual double perimeter() = 0; virtual double area() = 0; }; //Clasa care implementeaza interfata Shape class Square: public Shape { protected: double L; public: double perimeter() { return 4 * L; } double area() { return L * L; } }; //Alta clasa care implementeaza interfata Shape class Circle: public Shape { protected: double R; public: double perimeter() { return 2 * 3.14 * R; } double area() { return 3.14 * R * R; } };
Spre exemplu, daca avem obiecte de tip Student si obiecte de tip Angajat, putem declara o interfata pe care cele doua tipuri sa o mosteneasca. Astfel putem declara o lista de angajati si studenti.
In cazul in care destructorul nu este adaugat si implementat, destructorul clasei derivate nu va putea fi apelat niciodata.
//Interfata class BazaAbstracta { public: virtual ~BazaAbstracta() = 0; //Destructor virtual }; //Implementare destructor virtual BazaAbstracta::~BazaAbstracta() {} //Clasa derivata class Derivata: public BazaAbstracta { //Membrii, metode };