Constructori și destructori

Dacă, în exemplu dat anterior, am apela, înaintea funcției citire, funcția arie, am obține un rezultat indefinit, cam ca în cazul operațiilor cu variabile din tipuri simple, înainte de a le citi ori inițializa. O rezolvare a acestei probtleme se face prin includerea unei funcții speciale, numite constructor, instanțiată de fiecare dată când un obiect al clasei este creat. Această funcție are rolul de a inițializa datele membru și de a aloca spațiu de memorie pentru obiecte.

Declararea constructorului se face identic cu a celorlalte metode. Totuși, el rebuie să poarte același nume cu clasa. O altă particularitate este că funcția constructor nu folosește instrucțiunea return.

Pentru exemplul dat anterior, o declarare a constructorului s-ar putea scrie astfel: dreptunghi(int, int). Evident, domeniul constructorului trebuie să fie public.

Definirea acestei funcții ar cuprinde instrucțiuni de inițializare a datelor membru: dreptunghi::dreptunghi(int lat, int lung){ latime=lat; lungime=lung;} În această ipoteză, o declarare a unui obiect din această clasă poate fi făcută într-o sintaxă de felul:

dreptunghi a(4,6); Implementarea unei astfel de metode, face inutilă, în mod evident, existența metodei citire. Cu atât mai mult cu cât, prin utuilizarea constructorului, valorile inițiale sunt transmise imediat, încă din momentul creării obiectului.

Totuși, având în vedere că funcția constructor nu se poate apela ulterior, ci doar în momentul declarării obiectelor, prezența unor metode de modificare a datelor membru poate fi un instrument util.

În practică este necesar, de multe ori, ca funcțiile constructor să aibă mai multe forme. Acest lucru se realizează prin supraîncărcarea lor. Putem avea, astfel, constructor fără parametri, care inițializează obiectele cu anumite valori prestabilite (acesta se numește constructor implicit), constructori ca cei creați anterior, în care utilizatorul precizează valorile de inițializare, ori constructori de copiere, caz în care parametrul este un obiect de același tip, de la care noul obiect se initițializează. Evident, în funcție de situație, compilatorul alege constructorul potrivit. Continuăm exemplul, prin adăugarea de supradefiniții constructorului. dreptunghi(void); dreptunghi(const dreptunghi &); Implementarea celor două funcții poate arăta, în acest moment, astfel: dreptunghi::dreptunghi { latime=0; lungime=0; } dreptunghi::dreptunghi(const dreptunghi &d) { latime=d.latime; lungime=d.lungime; } Iată, mai jos, un exemplu de utilizare a celor trei forme de constructori definiți: dreptunghi a(2,6), b, c(a); Evident, obiectul a va fi inițializat cu valorile 2, respectiv 6, obiectul b cu valoarea 0, pentru ambele date membru, iar obiectul c cu valorile 2 și 6, preluate prin constructor de la obiectul a.

Modalitatea de a apela constructorul în paranteze rotunde poartă numele de formă funcțională. Mai nou, limbajul C++ acceptă și inițializarea uniformă, folosind parantezele acolade, ceea ce crește lizibilitatea codului, eliminând riscul de a confunda constructorul cu apelul altor metode. Pentru un obiect din clasa dreptunghi, o inițializare uniformă se poate face astfel: dreptunghi d{10, 20}; Mai trebuie precizat că este acceptată, pentru constructoriii cu un singur argument, utilizarea operatorului de atribuire.

Dacă utilitatea funcției constructor a fost lămurită, ar trebui  acum să spunem că ea are un corespondent și anume, funcția destructor. Importanța acesteia este mai mică în cazul alocărilor statice a datelor (cănd se utilizează tipuri fundamentale, ori vectori etc), dar crește în situația în care alocarea se face dinamic, cum este cazul structurilor dinamice: liste, stive, cozi. În această situație, funcția destructor are rolul important de a elibera memoria.

Pentru clasa dreptunghi, un exemplu de funcție destructor poate fi scris astfel: ~dreptunghi{ cout<<”a fost distrus obiectul”;} Practic, funcția nu face decât să afișeze un mesaj, pentru că datele au fost alocate static, iar de eliberarea efectivă a memoriei se va ocupa C++.