Constructori și destructori
Până acum, creării obiectelor urma un pattern ciudat:
Punct p;
p.x = 3;
p.y = 4;Creez obiectul, apoi îl inițializez manual setând fiecare câmp. E greoi, ușor de uitat, și câteva câmpuri pot rămâne necinstiate (cu valori aleatoare).
Constructorul rezolvă asta: o metodă specială care se apelează automat când creezi un obiect, inițializând datele în mod controlat.
Constructorul: definiție
Constructorul e o metodă specială care:
- Are același nume ca clasa
- Nu are tip de return (nici măcar
void) - Se apelează automat la crearea obiectului
class Punct {
public:
double x, y;
// Constructor
Punct() {
x = 0;
y = 0;
}
};
int main() {
Punct p; // constructor apelat automat - x = 0, y = 0
}Tipuri de constructori
1. Constructor implicit (default)
Fără parametri. Se apelează când scrii
Punct p;.
class Punct {
public:
double x, y;
Punct() {
x = 0;
y = 0;
}
};2. Constructor cu parametri
Primește valori inițiale pentru câmpuri.
class Punct {
public:
double x, y;
Punct(double xInit, double yInit) {
x = xInit;
y = yInit;
}
};
int main() {
Punct p(3, 4); // x = 3, y = 4
}3. Constructor de copiere (copy constructor)
Se apelează când creezi un obiect dintr-un altul.
class Punct {
public:
double x, y;
Punct(double xInit, double yInit) {
x = xInit;
y = yInit;
}
Punct(const Punct &altul) {
x = altul.x;
y = altul.y;
}
};
int main() {
Punct p1(3, 4);
Punct p2 = p1; // copy constructor
Punct p3(p1); // echivalent
}4. Constructorul implicit generat de compilator
Dacă nu scrii niciun constructor,
compilatorul îți generează unul gol automat.
Asta explică de ce Punct p; funcționează chiar fără
să scrii constructor.
Dar atenție: dacă scrii orice constructor, compilatorul nu mai generează cel implicit.
class Punct {
public:
double x, y;
Punct(double xInit, double yInit) { x = xInit; y = yInit; }
};
int main() {
Punct p; // EROARE - nu mai există constructor fără parametri
Punct p(3, 4); // OK
}Constructori multipli (overloading)
Poți avea mai mulți constructori, atâta timp cât au semnături diferite (parametri diferiți):
class Punct {
public:
double x, y;
Punct() { // implicit
x = 0; y = 0;
}
Punct(double a) { // cu un parametru
x = a; y = a;
}
Punct(double a, double b) { // cu doi parametri
x = a; y = b;
}
};
int main() {
Punct p1; // apelează Punct()
Punct p2(5); // apelează Punct(double)
Punct p3(3, 4); // apelează Punct(double, double)
}Compilatorul alege automat constructorul potrivit după parametrii oferiți.
Lista de inițializare (initializer list)
În loc să atribui în corpul constructorului, poți folosi o listă de inițializare:
class Punct {
public:
double x, y;
Punct(double xInit, double yInit) : x(xInit), y(yInit) {}
};De ce e mai bună lista de inițializare?
- Mai eficient - inițializarea directă, nu “atribuire peste valoare default”.
- Obligatoriu pentru:
- Câmpuri
const - Câmpuri referință
- Apelul constructorului clasei părinte (la moștenire)
- Câmpuri
class Cerc {
public:
Cerc(double rInit) : raza(rInit), PI(3.14159) {}
private:
double raza;
const double PI; // const - OBLIGATORIU lista de inițializare
};Atribuire vs lista de inițializare
// Atribuire în corp (mai puțin eficient)
Punct(double a, double b) {
x = a; // x e deja creat cu valoare default, apoi atribuim
y = b;
}
// Lista de inițializare (mai eficient)
Punct(double a, double b) : x(a), y(b) {}Inițializarea implicită a câmpurilor (C++11+)
Din C++11, poți seta valori default direct lângă declarație:
class Punct {
public:
double x = 0;
double y = 0;
// Constructorul implicit le folosește automat
};
int main() {
Punct p; // x = 0, y = 0
}Foarte util - nu mai scrii constructor default dacă toate câmpurile au default.
Valori default pentru parametri
Poți combina un singur constructor cu valori default:
class Punct {
public:
double x, y;
Punct(double xInit = 0, double yInit = 0) : x(xInit), y(yInit) {}
};
int main() {
Punct p1; // x = 0, y = 0
Punct p2(5); // x = 5, y = 0
Punct p3(3, 4); // x = 3, y = 4
}Un singur constructor acoperă 3 cazuri. Mai compact.
Destructorul: definiție
Destructorul e o metodă specială care:
- Are numele
~NumeClasa(cu tilda în față) - Nu primește parametri
- Nu are tip de return
- Se apelează automat când obiectul e distrus
(iese din scope,
delete, etc.)
class Test {
public:
Test() {
cout << "Creat\n";
}
~Test() {
cout << "Distrus\n";
}
};
int main() {
Test t; // afișează "Creat"
// la sfârșitul main, se apelează destructorul → afișează "Distrus"
}Ieșire:
Creat
Distrus
La ce folosește destructorul?
Principala folosire: eliberarea resurselor alocate dinamic.
class Vector {
public:
Vector(int capacitate) {
date = new int[capacitate];
dim = capacitate;
}
~Vector() {
delete[] date; // eliberăm memoria
}
private:
int *date;
int dim;
};Fără destructor, ai memory leak - memoria
alocată cu new nu e niciodată eliberată.
Alte resurse eliberate de destructor
- Fișiere deschise (
close()) - Conexiuni de rețea
- Mutex-uri / locks
- Memorie alocată dinamic
Ordinea apelurilor
La creare
- Câmpurile se inițializează în ordinea declarării (nu ordinea din lista de inițializare!).
- Apoi corpul constructorului se execută.
La distrugere
- Corpul destructorului se execută.
- Câmpurile se distrug în ordine inversă declarării.
class Test {
public:
Test() { cout << "Test creat\n"; }
~Test() { cout << "Test distrus\n"; }
};
int main() {
Test a, b, c;
// Ieșire: Test creat (x3)
// La final: Test distrus (x3) - în ordinea c, b, a
}Ciclul de viață al unui obiect
┌─────────────────────┐
│ CREARE │
│ (constructor) │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ UTILIZARE │
│ (apeluri metode) │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ DISTRUGERE │
│ (destructor) │
└─────────────────────┘
Exemplu complet: clasa Dreptunghi
#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
class Dreptunghi {
public:
// Constructor cu valori default
Dreptunghi(double l = 0, double w = 0) : lungime(l), latime(w) {
fout << "Creat dreptunghi " << lungime << "x" << latime << "\n";
}
// Destructor
~Dreptunghi() {
fout << "Distrus dreptunghi " << lungime << "x" << latime << "\n";
}
double arie() { return lungime * latime; }
private:
double lungime, latime;
};
int main() {
Dreptunghi d1(5, 3); // apel constructor cu 2 args
Dreptunghi d2(4); // apel cu 1 arg (latime default 0)
Dreptunghi d3; // apel fara args (ambele default 0)
fout << "Aria lui d1: " << d1.arie() << "\n";
return 0;
}date.out:
Creat dreptunghi 5x3
Creat dreptunghi 4x0
Creat dreptunghi 0x0
Aria lui d1: 15
Distrus dreptunghi 0x0
Distrus dreptunghi 4x0
Distrus dreptunghi 5x3Observă ordinea inversă la distrugere.
Exemplu cu resurse: VectorDinamic
#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
class VectorDinamic {
public:
VectorDinamic(int cap) : capacitate(cap), dim(0) {
date = new int[capacitate];
fout << "Alocat vector de " << capacitate << "\n";
}
~VectorDinamic() {
delete[] date;
fout << "Eliberat vector\n";
}
void adauga(int x) {
if (dim < capacitate) date[dim++] = x;
}
void afisare() {
for (int i = 0; i < dim; i++) fout << date[i] << " ";
fout << "\n";
}
private:
int *date;
int capacitate;
int dim;
};
int main() {
VectorDinamic v(5);
v.adauga(10); v.adauga(20); v.adauga(30);
v.afisare();
return 0;
}date.out:
Alocat vector de 5
10 20 30
Eliberat vectorDacă uit destructorul, memoria nu se eliberează și ai memory leak. Cu destructor, totul e curățat automat la final.
Constructori
explicit
Uneori constructorii cu un parametru pot crea conversii implicite nedorite:
class Temperatura {
public:
Temperatura(double c) { celsius = c; }
double celsius;
};
void proceseaza(Temperatura t) { }
int main() {
proceseaza(25.0); // OK - 25.0 convertit automat la Temperatura
}Pentru a preveni asta, folosim
explicit:
class Temperatura {
public:
explicit Temperatura(double c) { celsius = c; }
double celsius;
};
int main() {
proceseaza(25.0); // EROARE acum
proceseaza(Temperatura(25.0)); // OK explicit
}Bun pentru a preveni conversii accidentale.
Greșeli frecvente
1. Constructor cu
void sau tip de return
class Punct {
void Punct() { } // GRESIT - constructorul nu are return
Punct() { } // CORECT
};2. Uitarea
listei de inițializare pentru const
class Cerc {
public:
Cerc() {
PI = 3.14; // EROARE - nu poți atribui la const
}
const double PI;
};
// CORECT:
class Cerc {
public:
Cerc() : PI(3.14) { }
const double PI;
};3. Uitarea [] la
delete
~Vector() {
delete date; // GRESIT - pentru array folosești delete[]
delete[] date; // CORECT
}4. Memory leak - lipsa destructorului
class Nume {
char *nume;
public:
Nume(const char *n) {
nume = new char[100];
// ... copiez n în nume
}
// LIPSA destructorului → memory leak!
};5. Shallow copy
class Vector {
public:
Vector(int n) { date = new int[n]; }
~Vector() { delete[] date; }
int *date;
};
int main() {
Vector v1(10);
Vector v2 = v1; // PROBLEMA - ambele au același pointer
// La ieșire, ambele destruc același pointer → CRASH
}Soluție: scrii constructor de copiere care copiază și datele, nu doar pointerul. Avansat - vezi “rule of three”.
6. Apelarea explicită a constructorului
Punct p;
p.Punct(); // GRESIT - nu apelezi constructorul manualConstructorul se apelează doar automat la creare.
Rule of Three (Cinci)
Dacă clasa ta are alocare dinamică (new / delete), probabil trebuie să definești:
- Destructorul - eliberează memoria
- Constructorul de copiere - face deep copy
- Operatorul de atribuire - face deep copy la atribuire
În C++11+, se adaugă și:
- Move constructor
- Move assignment
Pentru clase simple fără alocare dinamică, compilatorul generează toate astea corect. Grija apare când manipulezi memoria manual.
Ce să reții
- Constructor = metodă specială apelată la crearea obiectului. Nume = clasa, fără return.
- Destructor = metodă specială apelată la
distrugere. Nume =
~Clasa, fără params. - Constructori pot avea parametri, pot fi multipli (overloading), pot avea valori default.
- Lista de inițializare
: x(a), y(b) { }e mai eficientă decât atribuirea în corp. - Necesară pentru câmpuri const și referință.
- Destructorul e crucial pentru eliberarea resurselor (new, fișiere, etc.).
- Câmpurile se inițializează în ordinea declarării (nu lista), se distrug în ordine inversă.
explicitprevine conversii implicite.- Rule of Three: dacă ai destructor, probabil vrei și constructor de copiere + operator de atribuire.