virtual în C++

Marchează o metodă care poate fi suprascrisă în clasele derivate; apelul printr-un pointer sau o referință la bază alege varianta tipului real.

Sintaxă
virtual void metoda();            // poate fi suprascrisă
virtual void metoda() = 0;        // pur virtuală: clasa devine abstractă
virtual ~Baza() = default;        // destructor virtual

Ce face virtual

Polimorfismul înseamnă că același apel face lucruri diferite în funcție de obiect. Dacă Forma are virtual double aria() const, iar Cerc și Dreptunghi o suprascriu, atunci forma.aria() printr-o referință Forma& apelează varianta potrivită obiectului real, decisă la rulare.

O metodă virtual ... = 0 este pur virtuală: nu are implementare în bază, iar clasa devine abstractă (nu poți crea obiecte din ea, doar din clase derivate care implementează metoda).

Orice clasă de bază folosită polimorfic trebuie să aibă destructor virtual. Altfel, la delete printr-un pointer la bază, destructorul clasei derivate nu este apelat. În clasele derivate, marchează suprascrierile cu override.

Exemple

Exemplul 1: Forme geometrice

Vectorul ține pointeri la Forma, dar fiecare apel aria() ajunge la clasa reală. Dreptunghi nu suprascrie nume(), deci pentru el se folosește varianta din bază („formă”).

#include <memory>
#include <numbers>
#include <print>
#include <vector>

class Forma {
public:
    virtual ~Forma() = default;
    virtual double aria() const = 0;          // pur virtuală
    virtual const char* nume() const { return "formă"; }
};

class Cerc : public Forma {
public:
    explicit Cerc(double r) : r_(r) {}
    double aria() const override { return std::numbers::pi * r_ * r_; }
    const char* nume() const override { return "cerc"; }
private:
    double r_;
};

class Dreptunghi : public Forma {
public:
    Dreptunghi(double l, double h) : l_(l), h_(h) {}
    double aria() const override { return l_ * h_; }
private:
    double l_, h_;
};

int main() {
    std::vector<std::unique_ptr<Forma>> forme;
    forme.push_back(std::make_unique<Cerc>(1));
    forme.push_back(std::make_unique<Dreptunghi>(3, 4));
    for (const auto& f : forme) {
        std::println("{}: aria {:.2f}", f->nume(), f->aria());
    }
}

Rezultat

cerc: aria 3.14
formă: aria 12.00

Exemplul 2: Cu și fără virtual

sunet este virtuală, deci se apelează varianta din Caine. tip nu este virtuală, deci prin referința la Animal se apelează varianta din bază.

#include <print>

struct Animal {
    virtual ~Animal() = default;
    virtual const char* sunet() const { return "..."; }
    const char* tip() const { return "animal"; }
};

struct Caine : Animal {
    const char* sunet() const override { return "Ham!"; }
    const char* tip() const { return "câine"; }
};

void prezinta(const Animal& a) {
    std::println("{} face {}", a.tip(), a.sunet());
}

int main() {
    Caine rex;
    prezinta(rex);
    std::println("Direct: {} face {}", rex.tip(), rex.sunet());
}

Rezultat

animal face Ham!
Direct: câine face Ham!

Exemplul 3: Destructorul virtual

Obiectul este distrus printr-un pointer la bază. Pentru că destructorul bazei este virtual, rulează întâi destructorul derivatei, apoi cel al bazei.

#include <memory>
#include <print>

struct Baza {
    virtual ~Baza() { std::println("~Baza"); }
};

struct Conexiune : Baza {
    ~Conexiune() override { std::println("~Conexiune: închid conexiunea"); }
};

int main() {
    std::unique_ptr<Baza> p = std::make_unique<Conexiune>();
    p.reset();
    std::println("gata");
}

Rezultat

~Conexiune: închid conexiunea
~Baza
gata

Greșeli frecvente

  • Uiți destructorul virtual în clasa de bază. Ștergerea printr-un pointer la bază (delete p sau unique_ptr<Baza>) este comportament nedefinit, iar în practică destructorul derivatei nu rulează.
  • Transmiți obiectul derivat prin valoare unei funcții care primește baza (void f(Animal a)). Obiectul este „tăiat” (slicing): rămâne doar partea de bază, iar metodele virtuale apelează varianta din bază. Folosește referință sau pointer.
  • Scrii în derivată o metodă cu o semnătură puțin diferită (de exemplu fără const). Nu mai suprascrie, ci creează o metodă nouă. Pune override și compilatorul îți va semnala greșeala.

Comenzi înrudite

  • override, final: override cere compilatorului să verifice că metoda chiar suprascrie una virtuală; final oprește suprascrierea sau moștenirea mai departe.
  • class: Definește un tip nou care grupează date (membri) și funcțiile care lucrează cu ele (metode); baza programării orientate pe obiecte.
  • public, private, protected: Specificatorii de acces: stabilesc cine poate folosi membrii unei clase și cum se moștenește o clasă de bază.
  • static_cast, dynamic_cast, const_cast, reinterpret_cast: Cele patru conversii explicite: static_cast (obișnuite), dynamic_cast (clase), const_cast (const) și reinterpret_cast (biți).
  • typeid: Dă informații despre tipul unei expresii la rulare; pentru clase cu funcții virtuale arată tipul real al obiectului.
  • std::unique_ptr: Pointer inteligent care deține singur un obiect și îl șterge automat când iese din domeniu.