You are on page 1of 16

Mihai TOGAN

Cap. 3 – Constructori și destructori

Obiective:
 Iniţializarea obiectelor prin constructori
 Definiție, proprietăţi pentru constructori, apelulul constructorilor
 Tipuri de constructori (impliciți, expliciţi)
 Clase încuibărite, apelul explicit al constructorilor, ordinea de apel
 Constructorul de copiere, necesitatea acestuia
 Situaţii de instanţiere a constructorului de copiere
 Iniţializarea tipurilor de date standard (simple) ȋn C++
 Distrugerea (cleanup) obiectelor prin destructori, definiție, proprietăţi pentru
destructori, apelul destructorilor, necesitatea destructorilor
 Apelulul constructorilor/destructorilor la folosirea operatorilor de alocare şi
dezalocare de memorie new și delete

1
Mihai TOGAN
Problema iniţializării obiectelor
 Iniţializarea structurii de date a obiectului (instanță a unei clase) încă de la momentul
creării sale.

/******************************************************************************/
class Vector {

char nume[32]; /* numele vectorului */


Point start; /* punctul de start al vectorului (originea) */
Point end; /* punctul de final al vectorului (destinatia) */

public:
Vector () /* constructor pentru clasa Vector */
{
strcpy (nume, "vector");
start.set (0, 0);
end.set (0, 0);
}

void set (const char *nume, int xs, int ys, int xe, int ye);
void print ();
};

void Vector::print()
{
cout << "\nDesenam vectorul '" << nume << "': ";
start.print (); cout << " --> "; end.print ();
}

void Vector::set(const char *nume, int xs, int ys, int xe, int ye)
{
cout << "\nInitializam vectorul '" << nume << "'...";
strncpy (this->nume, nume, sizeof(this->nume));
start.set (xs, ys);
end.set (xe, ye);
}
/******************************************************************************/
void main ()
{
//...
Vector V1;
cout << endl;
V1.print ();
//...
}

/******************************************************************************/

2
Mihai TOGAN
Constructori:

 Sunt metode (funcții) speciale ale claselor, responsabile cu operaţiile de iniţializare


necesare la nivelul obiectelor.
 Sunt metode apelate în mod automat (transparent) de către compilator, la declararea
obiectelor (instanţierea claselor).
 Sunt metode executate imediat dupa alocarea de memorie necesară pentru memorarea
datelor obiectului.
 Metodele de tip constructor (reguli):
o au în mod obligatoriu acelaşi nume ca și clasa:
Nume_Clasa (<lista de paramterii>)

o pot avea parametrii (nu este însă obligatoriu)


o nu returnează absolut nimic (nici void)

 La nivelul unei clase, pot co-exista mai multe metode de tip constructor:
o Metodele diferă prin lista de parametrii.
o Metodele pot implementa secvenţe de cod diferite (iniţializează ȋn mod diferit
obiectele).
o La construcţia unui obiect, este implicat (executat) un singur constructor.
Alegerea acestuia se face de compilator la build time, ȋn funcţie de contextul
declaraţiei obiectului.

 Constructorii pot folosi apeluri ale altor funcții (funcţii membre sau standalone).

Exemplu:

/******************************************************************************/
class Point { /* Declaratia clasei Point */
int x;
int y;
public:
void set(int a, int b) { /* funcție (metoda) inline */
cout << "\nInitializare punct cu coordonate noi: " << a << ", " << b << "...";
x = a;
y = b;
}
void print();
void move (int off_x, int off_y);
int getx() const {return x;}
int gety() const {return y;}
};

/* Implementare (definiție) clasa Point */


void Point::move (int off_x, int off_y) {
this->x = this->x + off_x;
this->y = this->y + off_y;
}
void Point::print() {
cout << "(" << x << ", " << y << ")";
}

3
Mihai TOGAN
/******************************************************************************/
class Vector { /* Declaratia clasei Vector */

char nume[32]; /* numele vectorului */


Point start; /* punctul de start al vectorului (originea) */
Point end; /* punctul de final al vectorului (destinatia) */

public:
Vector ();
Vector (const char *nume, int xs, int ys, int xe, int ye);

void set (const char *nume, int xs, int ys, int xe, int ye);
void print ();
};

Vector::Vector ()
{
cout << "\nConstructor fara parametrii (implicit)";
cout << "\nInitializam vectorul avand valori fixe:'V', (0, 0), (0, 0)";
strcpy (nume, "V");
start.set (0, 0);
end.set (0, 0);
}

Vector::Vector (const char *nume, int xs, int ys, int xe, int ye)
{
cout << "\nConstructor cu parametrii (explicit)";
cout << "\nInitializam vectorul '" << nume << "'...";

strncpy (this->nume, nume, sizeof(this->nume));


start.set (xs, ys);
end.set (xe, ye);
}

void main ()
{
Vector V1; /* se apeleaza constructorul fara parametrii) */
V1.print ();
//...
Vector V2("V2",1,2,2,4); /* se apeleaza constructorul cu parametrii) */
cout << endl;
V2.print();
}

4
Mihai TOGAN
Observaţie:
Constructorii pot avea orice tip de parametrii: tipuri de bază, tipuri definite (structuri, clase,
inclusiv clasa de care aparţin), pointeri, referinţe, etc. (ca orice altă funcție sau metodă).

Exemplu:
class Vector { /* Declaratia clasei Vector */
//...
public:

Vector ();
Vector (const char *nume, int xs, int ys, int xe, int ye);
Vector (const char *nume, const Point& start, const Point& end);
//...
void set (const char *nume, int xs, int ys, int xe, int ye);
};

Vector::Vector (const char *nume, const Point& start, const Point& end)
{
cout << "\nConstructor cu parametrii (explicit)";
cout << "\nInstanta noua de vector creata. Initializam vectorul '" << nume <<
"'...";

set (nume, start.getx(), start.gety(), end.getx(), end.gety());


}

//...
void main ()
{
Point P1, P2;
//...
Vector V1 ("V1", P1, P2);
V1.print ();
//...
}

5
Mihai TOGAN
Observaţii:
 Q: punctele P1 și P2 par a nu fi iniţializate, nu ?! A: ba da, insa cu valori default !!
 În lipsa altui constructor, orice clasă are un constructor implicit (care nu execută nimic)
 fara parametrii, fara linii de cod: Point () { }

Soluţia pentru corectare: adăugăm constructori (cel puţin unul) la nivelul clasei Point:

class Point {
int x;
int y;
public:
Point () {
x = 0; y = 0;
}
//...
};

//...
void main ()
{
Point P1, P2;
//...
Vector V1 ("V1", P1, P2);
V1.print ();
//...
}

 Constructorii pot fi :
o De tip implicit (default), dacă nu are parametrii.
o De tip explicit, dacă are parametrii.

 Daca nu există nici măcar un constructor implementat (implicit sau explicit), întotdeauna
exista acel constructor default (și empty) generat de compilator la build-time.
 Dacă se declară cel puţin un constructor (implicit sau explicit), compilatorul nu mai
crează constructorul default (și empty)
 Daca nu nu am constructor implicit (fara param), nu se pot crea obiecte fara valori
initiale.

Constructorii pot fi aplelaţi ca ȋn exemplul următor:


class Point { /* Declaratia clasei Point */
int x;
int y;

6
Mihai TOGAN
public:
Point (); /* constructor implicit ȋn clasa Point */
Point (int a, int b); /* constructor explicit ȋn clasa Point */
//...
};

Point::Point ()
{
cout << "\nconstructor implicit in clasa Point ";
x = 0; y = 0;
}

Point::Point (int a, int b)


{
cout << "\nconstructor explicit in clasa Point ";
x = a; y = b;
}

/******************************************************************************/
class Vector { /* Declaratia clasei Vector */

char nume[32]; /* numele vectorului */


Point start; /* punctul de start al vectorului (originea) */
Point end; /* punctul de final al vectorului (destinatia) */

public:

Vector ();
Vector (const char *nume, int xs, int ys, int xe, int ye);
Vector (const char *nume, const Point& start, const Point& end);

void set (const char *nume, int xs, int ys, int xe, int ye);
void print ();
};

/* Implementarea constructorului cu apel explicit


al constructorului clasei incuibarite(agregate) */
Vector::Vector (const char *nume, int xs, int ys, int xe, int ye)
: start (xs, ys), end (xe, ye)
{
cout << "\nConstructor cu parametrii (explicit) in clasa Vector";
cout << "\nInitializam vectorul '" << nume << "'...";

strncpy (this->nume, nume, sizeof(this->nume));


// start.set(xs, ys); /* nu mai este necesar, se face prin constr.*/
// end.set(xe, ye); /* nu mai este necesar, se face prin constr.*/
}
//...

7
Mihai TOGAN
Observaţie: în exemplul de mai sus, se poate observa și ordinea de apel (automată) a
constructorilor, ȋn situaţia ȋn care o clasă conţine instanţe ale altei clase:
 Mai întâi, se creează obiectele încuibărite folosind apelurile constructorilor specifici.
 În final, se creează obiectul mare folosind apelul constructorului său.

Alt exemplu:

class Point { /* Declaratia clasei Point */


int x;
int y;
public:
// Point (); /* nu mai avem constructor implicit */
Point (int a, int b); /* avem insa un constructor explicit */
//...
};

//Point::Point ()
//{
// cout << "\nconstructor implicit ȋn clasa Point ";
// x = 0; y = 0;
//}

//...
void main ()
{
Point P; /* Aici se genereaza eroare de compilare (vezi mai jos) */
//...
}

Error C2512: 'Point' : no appropriate default constructor available...

/* La definirea constructorului implicit a lui Vector,


se genereaza aceeasi eroare */
Vector::Vector ()
{
//...
}

Soluţia:
void main ()
{
/* Invocam constructorul cu parametrii cu ceva valori */
Point P (0, 0); //...
}

/* La definirea constructorului implicit a lui Vector,


apelam constructorul explicit al lui Point cu parametrii */

Vector::Vector () : start(0, 0), end(0, 0)


{
//...
}

8
Mihai TOGAN
Concluzii:
 Orice alocare de obiect: invocarea automata a unui constructor.
 Trebuie sa existe cel putin un constructor „potrivit” ce poate fi apelat de compilator
(transparent) la crearea de obiecte (indiferent ca sunt sau nu incuibarite ȋn alte clase).

Constructorul de copiere

Daca modificăm putin clasa Vector (schimbăm tipul de date pentru numele vectorului):
class Vector { /* Declaratia clasei Vector */

// char nume[32];
char *nume; /* numele vectorului (acum pointer, deci trebuie alocat) */
Point start;
Point end;
};

Vector::Vector ()
{
cout << "\nConstructor fara parametrii (implicit) in clasa Vector";
cout << "\nInitializam vectorul avand valori fixe: 'vector', (0, 0), (0, 0)";

nume = new char [7];


strcpy (nume, "vector");
}

Vector::Vector (const char *nume, int xs, int ys, int xe, int ye)
: start (xs, ys), end (xe, ye)
{
cout << "\nConstructor cu parametrii (explicit) in clasa Vector";
cout << "\nInitializam vectorul '" << nume << "'...";

this->nume = new char [strlen (nume) + 1]


strcpy (this->nume, nume);
}
/******************************************************************************/
void main ()
{
Point P1, P2 (1, 2);
//...
P1.print();
P2.print();

Point P3 = P2; /* instantierea unui obiect Point și initializarea


sa pe baza altui obiect Point deja existent */
P3.print();

Vector V1 ("V1", 1, 2, 3, 4);

V1.print ();

Vector V2 = V1; /* instantierea unui obiect Vector și initializarea


sa pe baza altui obiect Vector deja existent */
V2.print();
}

9
Mihai TOGAN

Observaţie: are loc copierea bit-cu-bit (bitwise copy) dintr-o instanță ȋn alta.
 Aici, necesită explicaţie la tablă privind cele două obiecte.
 Atenţie, ȋn cazul vectorilor V1 și V2, ambele obiecte „împart” aceeaşi zonă de memorie
din heap, pentru nume (acesta conţine valoarea „V1”).
 Explicaţie: ȋn C++, pentru orice tip de date (clasă, etc.) există un aşa numit constructor
de copiere implicit (default copy constructor) care realizează operaţia de bitwise-copy.

 Se poate implementa și un constructor de copiere explicit care îl anulează pe cel


implicit. Acesta se va executa la fiecare clonare de obiect.

Soluţia (la problema de mai sus): implementarea constructorului de copiere!


class Vector {

char *nume;
Point start;
Point end;

public:

Vector ();
Vector (const char *nume, int xs, int ys, int xe, int ye);
Vector (const char *nume, const Point& start, const Point& end);

Vector (const Vector &V); /* declaratia constructorului de copiere */


//...
};

/* implementarea pentru constructorul de copiere */


Vector::Vector (const Vector &V)
{
cout << "\nConstructor de copiere in clasa Vector";
this->nume = new char [strlen (V.nume) + 1];
strcpy (this->nume, V.nume);

this->start.set (V.start.getx(), V.start.gety() );


this->end.set (V.end.getx(), V.end.gety() );
}

10
Mihai TOGAN
void main ()
{
//...
Vector V1("V1", 1, 2, 3, 4); /* obiectul V1 se creeaza prin invocarea
constructorului cu parametrii al clasei Vector*/

Vector V2 (V1); /* obiectul V2 se creeaza prin invocarea


constructorului de copiere al clasei Vector */
Vector V3; /* obiectul V3 se creeaza prin invocarea
constructorului fara parametrii al clasei Vector */
//...
}

Observaţii:
 La nivelul unei clase, putem avea un singur constructor de copiere; aceasta îl anulează pe
cel default.
 Variante de instanţiere a unui obiect pe baza altuia (clonarea obiectului iniţial):
//...
Vector V2 (V1); // se creeaza un obiect nou V2 prin clonarea lui V1:
// invocarea constructorului de copiere */

Vector V3 = V1; // se creeaza un obiect nou V3 prin clonarea lui V1:


// invocarea constructorului de copiere */

Atenţie la următoarele situaţii:

//Cazul 1:
Vector V4; // aici V4 se creaza (se apeleaza constructorul implicit
și NU constructorul de copiere)

V4 = V1; // V4 se copiaza din V1 (bitwise-copy asign operator)


// NU se apeleaza constructorul de copiere

//Cazul 2:
Vector &V5 = V1; // V5 este doar o referinta la un obiect existnt.
// In acest caz, NU se creeaza nici-un obiect nou, deci
// nu se apeleaza nici-un constructor

Alte cazuri unde apare apelul constructorului de copiere:


 Obiect transmis ca parametru într-o funcție ca valoare (nu ca referință)
 Obiect returnat dintr-o funcție prin valoare (nu ca referință):

Exemplu:
Apelul constructorului de copiere la apelulul unei funcții cu parametrul valoare (obiect) și la
întoarcerea unei valori (obiect) dintr-o funcție:

class Vector {
//...
public:
Vector ();
Vector (const char *nume, int xs, int ys, int xe, int ye);
Vector (const Vector &V);
//...

11
Mihai TOGAN
bool compareVector (Vector V);
Vector makeCopy ();
//...
};

/* bool Vector::compareVector (Vector V)


Se creaza o clona a obiectului de apel pe stiva, ȋn frame -ul funcției apelate */
bool Vector::compareVector (Vector V)
{
if (strcmp (this->nume, V.nume) != 0)
return false;

if (this->start.getx() != V.start.getx() ||
this->start.gety() != V.start.gety() ||
this->end.getx() != V.end.getx() ||
this->end.gety() != V.end.gety())
return false;

return true;
}

/* Vector Vector::makeCopy ()
La ieșirea din funcție, se creaza o clona a obiectului Y, pe stiva, ȋn frame -ul
apelantului. Clona se creaza prin constructorul de copiere (explicit, daca exista,
altfel implicit
*/
Vector Vector::makeCopy ()
{
Vector Y;
Y.set (nume, start.getx(), start.gety(), end.getx(), end.gety());
return Y;
}

/******************************************************************************/

void main ()
{
Vector V1 ("vec", 1, 2, 3, 4), V2;
Vector V3;

V2 = V1.makeCopy (); /* un apel al constructorului de copiere */


Vector V3 = V1.makeCopy (); /* ȋn mod normal doua apeluri ale const. de
copiere:
- unul pentru constructia obiectului returnat
- unul pentru constructia lui V3
(compilatorul face insa optimizare) */

/* Apel al const. de copiere (pt constructia param V(V3) )*/


if (V2.compareVector (V3) == true)
cout << "vectori identici";
else
cout << "vectori diferiti";
//...
}

/******************************************************************************/

12
Mihai TOGAN
Observaţie: necesită discuţie privind diferenţa când se face implementarea folosind referinţă:

Cazul 1

bool compareVector (Vector& V); /* NU se mai face transmitere prin valoare (vezi
referinta la tipul parametrului) */

Cazul 3

Vector& makeCopy (); /* NU mai face return prin valoare (vezi referinta la tipul
de return al funcției) */

/* Funcția trebuie implementata ȋn alt mod. De ce ?!? */


Vector& Vector::makeCopy ()
{
Vector *Y = new Vector;
Y->set (nume, start.getx(), start.gety(), end.getx(), end.gety());

/* aici NU se mai creaza clona obiectului intors (tipul este referinta */


return *Y;
}

Cazul 3

bool compareVector (Vector* V); /* Aici nici nu se poate pune problema copierii:
se trsnmite un pointer */
Vector* makeCopy (); /* NU se pune problema: return adresa (pointer) */

Observaţie: tipurile de date simple (standard) sunt considerate clase ȋn limbajul C++

int x = 1; // pentru x se apeleaza apeleaza constructorul de copiere al clasei int

int x (2); // corect ! pentru x se apeleaza apeleaza constructorul de copiere


(implict) al clasei int

double y (2.5); // corect ! pentru y se apeleaza apeleaza constructorul de copiere


(implicit) al clasei double

//...

Destructori
 Metoda speciala la nivelul unei clase, apelata automat la distrugerea obiectelor unei clase.
 Numele destructorului: ~Nume_Clasa ( ).
 Destructorul nu returneaza nimic, și nici nu primeste parametrii.
 La nivelul unei clase, poate exista un singur destructor.
 De regula, se ocupa de clean-up-ul obiectelor.
 Este apelat de compilatorul C++ ȋn mod automat (transparent), chiar inainte de
distrugerea obiectului din memorie.

 Observaţie : nu este obligatoriu sa fie prezent explicit (ca și ȋn cazul constructorilor,


există un destructor implicit care nu face nimic).
13
Mihai TOGAN

class Vector {

char *nume; /* alocat dinamic la crearea obiectului,


eliberat la distrugerea obiectului*/
Point start;
Point end;
//...
public:
//...
~Vector (); /* declaratia pentru destructor */
//...
};

Vector::~Vector ()
{
cout << "\nDestructor ȋn clasa Vector";
if (nume != NULL)
delete[] nume;
}

Observaţie:
 Discuţie privind necesitatea și mai acută a constructorului de copiere în această situatie.
 Prezenţa destructorului poate conduce la un crash al aplicaţie daca nu există constructorul
de copiere explicit.

Exemplu privind apelul destructorului:


/******************************************************************************/
Vector::Vector ()
{
cout << "\nConstructor FARA parametrii (implicit) in clasa Vector (obiectul
initializat: " << this << ")";
cout << "\nInitializam vectorul avand valori fixe: 'vector', (0, 0), (0, 0)";

nume = new char [7];


strcpy (nume, "vector");
}

/******************************************************************************/
// Implementarea constructorului cu apelul constructorului clasei incuibarite
(agregate)
Vector::Vector (const char *nume, int xs, int ys, int xe, int ye)
: start (xs, ys), end (xe, ye)
{
cout << "\nConstructor CU parametrii (explicit) in clasa Vector (obiectul
initializat: " << this << ")";
cout << "\nInitializam vectorul '" << nume << "'...";

this->nume = new char [strlen (nume) + 1];


strcpy (this->nume, nume);
}
/******************************************************************************/
// Implementarea constructorului de copiere pentru clasa Vector
Vector::Vector (const Vector &V)
{
cout << "\nConstructor de copiere in clasa Vector (obiectul initializat: " <<
this << ")";
this->nume = new char [strlen (V.nume) + 1];
strcpy (this->nume, V.nume);
14
Mihai TOGAN
this->start.set (V.start.getx(), V.start.gety() );
this->end.set (V.end.getx(), V.end.gety() );
}

/******************************************************************************/
void main ()
{
Point P1, P2 (1, 2);
//...
cout << endl;
P1.print();
cout << endl;
P2.print();

Point P3 = P2; // instantierea unui obiect și


// initializarea sa pe baza altuia existent deja
cout << endl;
P3.print();

Vector V1 ("V1", 1, 2, 3, 4);

V1.print ();

Vector V2 = V1;
V2.print();
//...
}
/******************************************************************************/

Operatorii new și delete

 new : creează spaţiul necesar de memorie ȋn heap și instanţiază constructorul (obiectul se


construieşte corect).
 delete: instantiază destructorul (clean-up) și eliberează spaţiul aferent obiectului din
memoria heap și instanţiază destructorul (obiectul se distruge corect).

15
Mihai TOGAN

Vector *pV1 = new Vector; /* instantiere dinamica a obiectului adresat de pV1; apel
de constructor implicit (fara param) */

Vector *pV2 = new Vector ("vec", 1, 2, 3, 4); /* instantiere dinamica a obiectului


adresat de pV2; apel de constructor explicit (cu param) */

Vector *pV3 = (Vector *) malloc sizeof (Vector); /* alocare spatiu de memorie; NU


se apeleaza constructorul */

delete pV1 ; /* distrugerea corecta a obiectului (se apeleaza destructor și apoi


se elibereaza memoria) */
free (pV3); /* doar eliberare de memorie (nu se apeleaza destructor) */

Vector *pMultimeVec1 = new Vector [10]; /* alocare dinamica de 10 vec (se


apeleaza const. pentru fiecare */

delete pMultimeVec1[]; /* se apeleeza destructorul pentru fiecare */


delete pMultimeVec1; /* se apeleeza destructorul doar pentru primul */

Sumar
 Limbajele OOP (ȋn particular limbajul C++) dispun de mecanisme avansate de iniţializare
a obiectelor bazate pe constructori.
 Există mecanisme avansate de cleanup a obiectelor, bazate pe destructori.
 Orice clasă are ȋn mod implicit un constructor (empty) și un constructor de copiere
(bitwise-copy);
 Orice clasa are un destructor (empty);
 Tipurile de bază (int, floate, double, char, unsigned char,…) sunt considerate de
compilator ca fiind clase.
 Constructorii pot avea parametrii şi nu returnează nimic.
 Destructorii sunt metode fară parametrii şi nu returnează nimic.
 La nivelul unei clase, putem avea mai multi constructori însă un singur destructor.
 Constructorul de copiere este necesar în cazul în care copierea de tip bitwise nu este
suficientă.
 De regulă, dacă avem zone de memorie alocate dinamic ȋn cadrul clasei (variabile
membre de tip pointeri), atunci devine absolut necesară prezenţa constructorului de
copiere.
 Pentru obiectele încuibărite, constructia pleacă din interior, cleanup-ul din exterior.
 Atentie la clasele încuibărite. Dacă nu au constructor implicit (fără parametrii), atunci
trebuie instanţiat ȋn mod explicit un constructor cu parametrii.
 Constructorul de copiere se apelează de asemenea la transmiterea de parametrii funcțiilor
la apel, şi respectiv la întoarcerea de valori (return valoare) din funcții.
 new, delete vs. malloc(), free()

16

You might also like