This is an old revision of the document!


Laboratorul 08: Mostenire multipla. Clase si functii abstracte. Interfete

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++:

  • Abstract Classes and Pure Virtual Functions (pag. 671-673)
  • Separate Interface and Implementation (pag. 264-265)

Mostenire Multipla

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

In acest caz, la declararea clasei derivate nu se specifica o singura clasa de baza, ci o succesiune de clase de baza, separate prin virgula, avand in fata specificatorii de acces.

class C:public A, public B

In candrul mostenirii multiple, constructorii claselor de baza vor fi apelati in ordinea enumerarii, iar destructorii vor fi apelati in ordine inversa.

Problema Diamantului

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

In momentul de fata, daca apelam metoda breathe() din Snake, acesta nu va sti ce metoda sa apeleze, daca sa fie cea suprascrisa in clasa Animal sau cea suprascrisa in clasa Reptile.

Cum rezolvam insa Problema Diamantului? Folosind derivarea virtuala.

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.

Pentru a se crea un obiect de tip Snake, se vor apela constructorii claselor Animal and Reptile, dar se va apela o singura data constructorul clasei LivingThing, astfel Snake va avea o singura instanta a clasei LivingThing.

Clase si Functii Abstracte

O metoda virtuala se numeste abstracta (pura) daca nu are implementare:

virtual tip_returnat metoda(lista parametrii) = 0;

O clasa se numeste abstracta daca are cel putin o metoda abstracta.

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.

Shape.cpp
#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;
}

  • Functiile abstracte sunt folosite in cazul in care metoda nu are o implementare in clasa de baza, insa dorim sa o declaram virtuala.
  • Nu se pot instantia obiecte de tipul unei clase abstracte, DAR se pot declara pointeri de acel tip.
  • Suntem obligati, ca eventual, sa implementam functia intr-o clasa derivata, altfel, si aceasta clasa derivata o sa fie tot abstracta.

Interfete

O clasa fara atribute si cu toate metodele abstracte se numeste interfata.

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

Interfetele pot fi folosite pentru a declara liste neomogene de obiecte diferite.

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.

Destructori virtuali puri/abstracti

Este absolut necesara declararea destructorilor virtuali abstracti!

In cazul in care destructorul nu este adaugat si implementat, destructorul clasei derivate nu va putea fi apelat niciodata.

poo-is/laboratoare/08.1601380363.txt.gz · Last modified: 2020/09/29 14:52 by eduard.ciurezu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0