Professional Documents
Culture Documents
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 {
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:
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;}
};
3
Mihai TOGAN
/******************************************************************************/
class Vector { /* Declaratia clasei Vector */
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 << "'...";
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 <<
"'...";
//...
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.
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;
}
/******************************************************************************/
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);
void print ();
};
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:
//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) */
//...
}
Soluţia:
void main ()
{
/* Invocam constructorul cu parametrii cu ceva valori */
Point P (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)";
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 << "'...";
V1.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.
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);
10
Mihai TOGAN
void main ()
{
//...
Vector V1("V1", 1, 2, 3, 4); /* obiectul V1 se creeaza prin invocarea
constructorului cu 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 */
//Cazul 1:
Vector V4; // aici V4 se creaza (se apeleaza constructorul implicit
și NU 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
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 ();
//...
};
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;
/******************************************************************************/
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) */
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++
//...
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.
class Vector {
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.
/******************************************************************************/
// 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 << "'...";
/******************************************************************************/
void main ()
{
Point P1, P2 (1, 2);
//...
cout << endl;
P1.print();
cout << endl;
P2.print();
V1.print ();
Vector V2 = V1;
V2.print();
//...
}
/******************************************************************************/
15
Mihai TOGAN
Vector *pV1 = new Vector; /* instantiere dinamica a obiectului adresat de pV1; apel
de constructor implicit (fara param) */
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