Laboratorul 06: Derivare

In cadrul acestui laborator, vom aprofunda inca un concept important al Programarii Orientate pe Obiecte, derivarea. Pe parcursul acestui laborator vom aborda notiuni precum: mostenire, ierarhie de clase, modificatori de acces, shadowing, upcasting, pragma once, redefinire.

Ca referinte externe, recomandam urmatorul capitol din Absolute C++:

  • Capitolul 14 (Chapter 14: Inheritance, pag. 613-661)

1. Introducere

Derivarea este o relatie intre clase de tipul “is a” / “is many”.

Mostenirea (numita si derivare) este un mecanism de refolosire a codului. Totodata, ofera posibilitatea de a defini o clasa care ,,extinde” o clasa existenta (la codul de baza din clasa existenta adaugandu-se noi atribute si/sau metode).

1.1. Exemplu

//cod
class Produs {
    private:
        char *nume;
        int pret;
        char cod[10];
};
 
class Electrocasnic {
    private:
        char *nume;
        int pret;
        char cod[10];
        int durata_garantie;
};
 
class Aliment {
    private:
        char *nume;
        int pret;
        char cod[10];
        int durata_expirare;
};
 
class Jucarie {
    private:
        char *nume;
        int pret;
        char cod[10];
        int varsta_recomandata[2];
};
 
class TV {
    private:
        char *nume;
        int pret;
        char cod[10];
        int durata_garantie;
        double diagonala;
};
 
class Masina_cafea {
    private:
        char *nume;
        int pret;
        char cod[10];
        int durata_garantie;
        char *tip; //expresor, filtru
};
/* iar exemplele pot continua */
//cod

1.2. Ce observam?

Putem observa ca avem o clasa de baza, Produs, si ca putem distinge mai multe categorii de produse. Aceste categorii de produse sunt niste clase care, pe langa faptul ca au comportamentul clasei Produs, au in plus atribute si comportamente specifice (clase specializate).

De exemplu: Masina_cafea si TV-ul sunt electrocasnice, dar acestea au comportamente (si atribute) specifice. (Un TV nu poate sa faca cafea, iar masina_cafea nu difuzeaza stirile de la ora 17:00)

Problema intampinata: Am rescris foarte mult cod. Cum fac sa evit asta?

2. Derivarea

2.1. Aplicare

//cod
class Produs { //clasa de baza
    protected:
        char *nume;
        int pret;
        char cod[10];
};
 
class Electrocasnic : public Produs {//clasa derivata (din Produs), dar si clasa de baza
    protected:
        int durata_garantie;
};
 
class Aliment : public Produs{//clasa derivata (din Produs)
    private://daca implementam o clasa care sa fie derivata din clasa ALiment, folosim protected 
        int durata_expirare;
};
 
class Jucarie : public Produs{//clasa derivata (din Produs)
    private://daca implementam o clasa care sa fie derivata din clasa Jucarie, folosim protected
        int varsta_recomandata[2];
};
 
class TV : public Electrocasnic{//clasa derivata (din Electrocasnic)
    private:
        double diagonala;
};
 
class Masina_cafea : public Electrocasnic{//clasa derivata (din Electrocasnic)
    private:
        char *tip; //expresor, filtru
};
/* iar exemplele pot continua */
//cod

Am rezolvat problema intampinata!

2.2. Concluzie

Acum putem afirma ca:

Derivarea este un procedeu prin care se creeaza un nou tip de date (o noua clasa) folosind o clasa existenta, mai exact, adaugam un cod nou (atribute noi, metode noi) codului deja existent ⇒ se preia interfata clasei de baza.

In clasa derivata sunt mostenite toate atributele si metodele clasei de baza si le pot accesa direct daca sunt declarate public sau protected.

2.3. Intrebari

1. Putem sa derivam mai multe clase din clasa de baza?

Da, se poate face acest lucru. Putem sa avem oricate clase derivate dintr-o clasa de baza si oricate niveluri de derivare (cat timp este logic).

2. Pot sa am mai multe clase parinte in acelasi timp?

Desigur, acest mecanism se numeste mostenire multipla. Exemplu:

Avem urmatoarele clase de baza:

  • Angajat (are salariu, speram ca mare, si lucreaza la o firma)
  • Student (are note, invata la o facultate)

Presupunem ca studentul nostru se angajeaza, astfel se creaza o noua clasa Student_Angajat (are note, are salariu, lucreaza la o firma si studiaza la o facultate). Observam ca noua clasa, Student_Angajat, are ca si parinti ambele clase (Angajat, Student).

3. Modificatori de acces (permisiuni)

In C++ avem 3 modificatori de acces:

  • public
  • private
  • protected

Public

Tot ce este declarat public este vizibil pentru toate lumea. Daca avem metode sau atribute publice intr-o clasa, le putem apela folosind operatorul “.”.

Private

Tot ce este declarat private este vizibil doar la nivel de clasa. Daca avem atribute declarate ca fiind publice, acestea sunt vizibile doar in clasa respectiva. De aceea, folosim metode prin care extragem valorile atributelor private.

Protected

Tot ce este declarat protected este inaccesibil din exteriorul clasei, exceptie facand doar clasele derivate dintr-o clasa cu atribute protected. Acestea, clasele derivate, pot sa acceseze atributele protected fara a fi nevoie de a folosi metode speciale care returneaza valori.

3.1. Exemplul 1

Public.cpp
#include <iostream>
using namespace std;
 
class Cerc {
    public:
        double raza;
        double arie_cerc() {
            return 3.14 * raza * raza;
        }
};
 
int main() {
    //Exemplu public
    Cerc circle;
    circle.raza = 6.5;
    cout << "Raza cercului este: "<< circle.raza << endl; //o sa afiseze 6.5
    cout << "Aria cercului este: "<< circle.arie_cerc() << endl; //o sa afiseze aria cercului
 
    return 0;
}
Private.cpp
#include <iostream>
using namespace std;
 
class Cub {
     private:
         int latura;
     public: 
         int arie_cub () {
             return 6 * latura * latura;
         }
};
 
int main() {
    //Exemplu private
    Cub cube;
    cube.latura = 5; /*incercam sa ii atribuim o valoare atributului 
                     latura din clasa Cub, inafara clasei
                     nu putem face acest lucru fara o metoda specifica */
    cout << "Aria cubului este: " << cube.arie_cub() << endl; //o sa afiseze o eroare
 
    return 0;
}
Protected.cpp
#include <iostream>
using namespace std;
 
class Parinte {
    protected:
        int id_clasa;
};
 
class Copil : public Parinte {
    public:
        void setId (int id) {
            id_clasa = id;
        }
        void print_id () {
            cout << "Id_copil: " << id_clasa;
        }
};
 
int main () {
    //Exemplu protected
    Copil obiect;
    obiect.setId(77);
    obiect.print_id(); //o sa afiseze ,,Id copil: 77"
 
    return 0;
}

3.2. Exemplul 2

Indicator de vizibilitate Accesibilitate Zona
public accesibil din exteriorul ierahiei
accesibil din clasele derivate
protected inaccesibil din exteriorul ierarhiei
accesibil din clasele derivate si orice alte clase derivate din clasele derivate
private inaccesibil din exteriorul ierarhiei
inaccesibil din clasele derivate
class A {
    public:
        int x;
    protected:
        int y;
    private:
        int z;
};
class B : public A { 
    // x este public 
    // y este protected 
    // z nu este accesibil din B 
}; 
 
class C : protected A { 
    // x este protected 
    // y este protected 
    // z nu este accesibil din C 
}; 
 
class D : private A { 
    // x este private 
    // y este private 
    // z nu este accesibil din D 
}; 

Derivarea este implicit privata (class Derivata : Baza).

Insa, vrem sa fie (aproape mereu) publica (class Derivata: public Baza).

Rareori este protected (class Derivata : protected Baza).

4. Comportamentul metodelor din clasa derivata

4.1. Constructorii

  • Din punct de vedere al ordinii, prima oara se apeleaza constructorul / constructorii din clasa de baza si apoi cei din clasa derivata.
  • In cazul in care clasa derivata nu are niciun constructor declarat, se genereaza unul default care va apela constructorul fara parametrii al clasei de baza (trebuie sa existe)
  • Daca in clasa derivata nu avem constructor de copiere, se genereaza unul automat care il apeleaza pe cel din clasa de baza (similar ca la punctul anterior)
  • Daca implementam un constructor pentru clasa derivata care nu apeleaza un constructor al clasei de baza, atunci, by default, se va apela constructorul fara parametrii al clasei de baza (acesta trebuie sa existe obligatoriu, daca nu ⇒ eroare)

4.2. Operatorul =

  • Daca este generat automat, sa va apela operatorul = din clasa (clasele) de baza. Insa, daca este implementat de noi, trebuie sa ne asiguram ca are acest comportament (de a apela operatorul = din clasa de baza).

4.3. Destructorul

  • Din punct de vedere al ordinii, prima oara se apeleaza destructorul din clasa derivata si apoi cel din clasa de baza.
  • Nu se face o apelare explicita deoarece exista un singur destructor in clasa de baza.

Contructorii, destructorii si functiile friend nu se mostenesc in clasele derivate.

In general, operatorii implementati ca functii membre se mostenesc. O exceptie este operatorul de atribuire, acesta nu se mosteneste.

Functiile friend pot accesa atributele si metodele protected/public/private din clasa cu care sunt prietene.

Observatii

Functiile membre statice se comporta ca orice functie membra, sunt mostenite in clasa derivata.

Nu pot fi virtuale (notiune aprofundata in cadrul laboratorului 7).

5. Agregare vs derivare

Amandoua mecanismele reutilizeaza codul scris pentru o clasa de baza/simpla intr-o alta clasa mai complexa. In ambele cazuri se folosesc liste de initializare pentru constructori pentru a crea obiectele de baza (chiar daca apelul difera prin sintaxa).

5.1. Diferente

Agregarea
  • folosita cand se doreste reutilizarea unui tip de date A pentru generarea altui tip de date B (fara a prelua interfata lui A)
  • se integreaza in clasa mai complexa un atribut (sau mai multe) de tipul clasei mai simple
  • utilizatorii noii clase vor vedea doar interfata acesteia
  • nu va mai fi de interes interfata clasei de baza
Derivarea
  • folosita cand se doreste preluarea interfetei clasei de baza
  • utilizatorul va vedea atat interfata clasei de baza cat si a celei derivate

Nu exista nicio diferenta in termeni de: memorie ocupata si durata de executie.

Avantajele derivarii

Putem sa adaugam cod nou fara a introduce bug-uri in codul existent.

Erorile se gasesc in codul nou ⇒ mai usor de cautat/gasit.

Clasele sunt clar separate. (Codul poate fi refolosit si in alte locuri fara a avea erori)

6. Mostenire multipla

6.1. Exemplu

#include <iostream>
using namespace std;
class Baza1 {
    protected:
        int atribut1;
    public: 
        Baza1(int i = 0) : atribut1(i){}
        void set_atribut1(int i) {
            atribut1 = i;
        }
        void afisare_atribut1() {
            cout << "\nAtribut1 = " << atribut1 << endl;
        }
};
class Baza2 {
    protected:
        int atribut2;
    public:
        Baza2(int i = 0) : atribut2(i){}
        void set_atribut2(int i) {
            atribut2 = i;
        }
        void afisare_atribut2() {
            cout << "\nAtribut2 = " << atribut2 << endl;
        }
};
class Derivata : public Baza1, public Baza2 {
    private:
        int atribut3;
    public: 
        Derivata() {}
        Derivata(int a1, int a2, int a3) : Baza1(a1),Baza2(a2),atribut3(a3){}
        void set_atribut3(int i) {
            atribut3 = i;
        }
        void set_atribute(int a1, int a2, int a3) {
            atribut1 = a1;
            atribut2 = a2;
            atribut3 = a3;
        }
        void afisare_atribute() {
            cout << "\nAtribut1 = " << atribut1 << "\nAtribut2 = "
             << atribut2 << "\nAtribut3 = " << atribut3 << endl;
        }
};
int main() {
    Derivata obiect(1,2,3);
    obiect.afisare_atribute();
    cout << "\n" << "________________" << endl;
    obiect.set_atr1(5);
    obiect.set_atr2(6);
    obiect.set_atr3(7);
    obiect.afisare_atribut1();
    obiect.afisare_atribute();
 
    return 0;
}

Cand utilizam mostenirea multipla putem intampina urmatoarele probleme:

  • atribute si metode cu acelasi nume in clasele de baza
  • derivare dubla indirecta din clasa de baza
  • altele

7. Shadowing, Upcasting

Shadowing
class A {
    protected: 
        int atr;
};
 
class B : public A {
    private:
        //am mostenit si atributul ,,atr" din clasa A
        int atr; // are priotate fata de atributul cu acelasi nume din A
    public:
        void set_atr (int i, int j) {
            A::atr = i; //pentru a avea acces la atributul ,,atr" din A
                        //il apelam folosind A::
            atr = j;
        }
};
Upcasting

Daca o clasa este derivata dintr-o alta clasa de baza (relatie “is a”) ⇒ obiectele de tipul clasei derivate, sunt, in acelasi timp, si obiecte de tipul clasei de baza.

Acest fapt ne permite urmatoarea atribuire:

obiect_clasa_baza = obiect_clasa derivata (upcasting, conversie la tipul obiectului de baza)

Daca facem acest lucru (obiect_clasa_baza = obiect_clasa derivata) vom pierde informatiile suplimentare stocate in obiect_clasa_derivata.

Este permisa atribuirea in care se ,,pierd date”. Nu sunt permise cele in care nu se stie cu ce sa se completeze campurile suplimentare (obiect_clasa derivata = obiect_clasa_baza).

Putem face acest lucru posibil (obiect_clasa_derivata = obiect_clasa_baza) doar daca supradefinim operatorul de atribuire din clasa derivata.

Ce se intampla daca intr-o clasa derivata o sa redefinim o metoda din clasa de baza?

Putem face acest lucru prin 2 metode:

  • redefinire/redefine (semnatura poate sau nu sa difere)
  • supraincarcare/override (avem aceeasi semnatura si tip returnat si functia de baza era virtuala)

In aceste cazuri, versiunile metodelor din clasa de baza o sa fie ascunse pentru noua clasa (nu avem acces direct la functiile clasei de baza cu acelasi nume ca cele din clasa derivata). Le putem apela explicit folosind nume_clasa_baza :: nume_functie.

8. Pragma Once

In cazul in care avem ierarhii complicate de clase, trebuie sa avem grija sa nu includem header-ul unei clase de mai multe ori in alt header sau in programul principal.

Putem evita acest lucru daca folosim directiva preprocesor #pragma once. Aceasta se scrie la inceputul header-ului.

header_clasa_baza.h
#pragma once //include aceasta sursa (header_clasa_baza.h) o singura data in momentul compilarii
#include <iostream>
using namespace std;
class Baza {
    protected:
        int atribut;
    public: 
        Baza(int i):atribut(i){}
};

Alternativa

header_clasa_baza.h
#ifndef HEADER_CLASA_BAZA
    #define HEADER_CLASA_BAZA
    #include <iostream>
    using namespace std;
    class Baza {
        protected:
            int atribut;
        public: 
            Baza(int i):atribut(i){}
    };
#endif
poo-is-aa/laboratoare/06.txt · Last modified: 2024/08/14 20:10 (external edit)
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