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 psauunique_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ă. Puneoverrideș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.