Subprograme și recursivitate

Moștenire și polimorfism în C++: virtual, override

Moștenirea permite definirea unui tip pornind de la altul, iar polimorfismul dinamic permite apelarea comportamentului potrivit printr-o referință sau un pointer la tipul de bază. Funcțiile virtuale stabilesc acest mecanism în C++.

Moștenirea permite definirea unui tip pornind de la altul, iar polimorfismul dinamic permite apelarea comportamentului potrivit printr-o referință sau un pointer la tipul de bază. Funcțiile virtuale stabilesc acest mecanism în C++.

Vom folosi o interfață pentru forme geometrice care pot calcula o arie. Dreptunghiul și pătratul oferă implementări diferite, iar funcția de afișare lucrează numai cu interfața comună. Exemplul urmărește relația dintre tipul static și obiectul concret.

Cum funcționează

Clasa Shape declară area() const ca funcție virtuală pură, prin =0. Ea este abstractă și nu poate fi instanțiată direct. O clasă derivată concretă trebuie să ofere implementările virtuale pure necesare. Moștenirea public exprimă că un obiect derivat poate fi folosit prin interfața de bază, cu respectarea contractului acesteia.

Rectangle și Square definesc area cu override. Acest cuvânt cere compilatorului să verifice că semnătura suprascrie într-adevăr o funcție virtuală din bază. O diferență aparent mică, precum lipsa lui const, poate crea altă funcție în locul celei dorite; override transformă greșeala într-un diagnostic de compilare.

Funcția show primește const Shape&. Apelul shape.area() este ales în funcție de tipul concret al obiectului, deci pentru un Rectangle execută formula lățime·înălțime, iar pentru Square latură·latură. Referința nu copiază obiectul și nu îi elimină partea derivată. Transmiterea prin valoare a unei baze concrete poate produce object slicing, pierderea părții derivate; o bază abstractă precum cea de aici nici nu poate fi copiată astfel ca obiect independent.

Destructorul bazei este virtual deoarece un obiect derivat poate fi distrus printr-un pointer la Shape. Ultimul exemplu folosește unique_ptr care deține un Square creat cu make_unique. Proprietarul eliberează automat obiectul la ieșirea din domeniu, iar destructorul virtual asigură distrugerea potrivită. Nu apelăm delete manual pentru un obiect deținut de unique_ptr.

Datele citite sunt dimensiuni întregi între zero și un milion, iar rezultatele încap în long long. Clasele presupun respectarea acestor limite. Într-o interfață generală, constructorii ar trebui să aplice politica aleasă pentru dimensiuni invalide. Nu folosim moștenire numai pentru a evita duplicarea a două câmpuri: justificarea este contractul comun de calcul al ariei.

Exemplu urmărit pas cu pas

Pentru lățime 3, înălțime 4 și latura 5, show(rectangle) afișează 12, iar show(square) afișează 25. Ultimul apel primește obiectul deținut prin pointerul de bază și afișează tot 25.

Codul funcției show nu conține un if pentru fiecare tip de formă. Adăugarea unei alte forme care respectă interfața poate reutiliza aceeași funcție. Aceasta nu înseamnă că orice problemă necesită o ierarhie; pentru date simple, o funcție sau o compoziție de obiecte poate fi mai potrivită.

Implementare C++17

#include <iostream>
#include <memory>
class Shape {
  public:
    virtual long long area() const = 0;
    virtual ~Shape() = default;
};
class Rectangle : public Shape {
    long long width_, height_;

  public:
    Rectangle(long long w, long long h) : width_(w), height_(h) {
    }
    long long area() const override {
        return width_ * height_;
    }
};
class Square : public Shape {
    long long side_;

  public:
    explicit Square(long long side) : side_(side) {
    }
    long long area() const override {
        return side_ * side_;
    }
};
void show(const Shape &shape) {
    std::cout << shape.area() << '\n';
}
int main() {
    long long w, h, s;
    std::cin >> w >> h >> s;
    Rectangle rectangle(w, h);
    Square square(s);
    show(rectangle);
    show(square);
    std::unique_ptr<Shape> owned = std::make_unique<Square>(s);
    show(*owned);
}

De ce funcționează și cât costă

Fiecare clasă concretă respectă contractul area și implementează formula sa. Dispecerizarea virtuală selectează implementarea obiectului real prin referința de bază. Proprietatea unică și destructorul virtual completează corect durata de viață a obiectului alocat.

Formulele exemplului au timp O(1). Un apel virtual poate avea un cost de indirectare, iar reprezentarea exactă depinde de implementare. Nu atribuim un număr universal de octeți fiecărui obiect și nu presupunem că polimorfismul implică obligatoriu alocare dinamică.

Greșeli frecvente

Nu omite destructorul virtual când ștergerea se face prin pointer la bază. Nu confunda override cu overload. Nu copia obiecte polimorfice prin valoarea bazei când trebuie păstrat comportamentul derivat. Preferă compoziția când relația nu exprimă substituirea unui tip prin altul.

Exersează

De ce show poate afișa aria unei noi clase Triangle fără un if suplimentar, dacă aceasta implementează corect Shape?

Rezolvare explicată: Funcția folosește contractul virtual area. Apelul selectează implementarea din Triangle prin obiectul concret, fără ca show să cunoască formula sau câmpurile noii clase.

Continuă să înveți

Clase și încapsulare, pointeri și parametri.

Întrebări frecvente

Ce face virtual?

Permite alegerea implementării în funcție de tipul concret al obiectului la apelurile polimorfice prin pointer sau referință la bază.

Ce verifică override?

Că metoda declarată suprascrie o funcție virtuală compatibilă din bază. Este util pentru detectarea diferențelor accidentale de semnătură.

Pot crea direct un obiect Shape din exemplu?

Nu. Clasa conține o funcție virtuală pură și este abstractă. Creezi un tip concret care implementează interfața.

De ce destructorul bazei este virtual?

Pentru distrugerea corectă a obiectelor derivate printr-un pointer la bază. Exemplul folosește această situație prin unique_ptr.

Polimorfismul cere mereu new?

Nu. Primele două obiecte au durată de viață automată și sunt transmise prin referință. Alocarea dinamică și dispecerizarea virtuală sunt concepte distincte.

Un pas mai departe

Se încarcă progresul…

Exersează cu o problemă →