Sunteți pe pagina 1din 150

OOP Curs

========
Daniel Dragulici
februarie - mai 2013
==============================================================================
Detalii administrative:
=======================
Notare:
- o nota la examenul scris (S);
- o nota la laborator (L);
Conditii de promovabilitate: S >= 5, L>=5
Nota finala la aceasta disciplina (F), care se trece in catalog, se
calculeaza dupa regula:
F = parte_intreaga_superioara((S + L) / 2) + bonus
unde "bonus" este 0 sau 1, valoarea 1 acordandu-se celor care s-au
remarcat in mod deosebit de-a lungul semestrului.
Bibliografie:
* Herbert Schildt: "C++ manual complet", Teora, 1997
* Muslea Ionut: "Initiere in C++ Programare orientata pe oiecte",
Ed. Cluj-Napoca, 1992, "C++ pentru avansati 33", Ed. Microinformatica,
1994
* Bjarne Stroustrup: "The C++ programming language", Addison-Wesley, 1997
(aparuta si la Ed. Teora)
* Bruce Eckel: "Thinking in C++" (2 vol), Prentice Hall (dar se gaseste
gratis pe Internet)
* ANSI/ISO C++ standard (nu este gratis, dar exista draft-uri gratis)
* diverse resurse pe Internet:
Wikipedia (http://en.wikipedia.org/wiki/Main_Page)
http://www.cplusplus.com/
http://publib.boulder.ibm.com/infocenter/lnxpcomp/
http://www.tutorialspoint.com
etc.
Materialele de referinta pentru examen sunt:
* Cartea lui Herbert Schildt mentionata in bibliografie
* Acest curs
(orice informatie prezenta in reuniunea acestor doua materiale poate fi
subiect de examen).
Lectia 1 - Conceptul de OOP

===========================
I. Paradigme ale programarii
============================
O PARADIGMA este un tipar de gandire si de concepte.
O PARADIGMA A PROGRAMARII este un stil fundamental de programare a
calculatoarelor. Principalele paradigme sunt:
- programarea orientata pe obiecte (OOP);
- programarea imperativa;
- programarea functionala;
- programarea logica.
Ele au ca fundament modele distincte de calcul:
- masina Turing, pentru programarea orientata pe obiecte si cea imperativa;
- lambda calculul, pentru programarea functionala;
- logica de ordinul intai, pentru programarea logica.
Exista si alte paradigme, avand intr-o masura mai mare sau mai mica
legatura cu cele patru de mai sus: programarea structurata si cea
procedurala (variante ale programarii imperative), programarea declarativa
(opusa celei imperative), calculul paralel si concurent, etc.
- PROGRAMAREA IMPERATIVA descrie calculul in termenii unor INSTRUCTIUNI
ce schimba STAREA programului; baza programarii imperative este
instructiunea de ATRIBUIRE; programul este constituit din secvente de
comenzi (instructiuni) pe care le are de executat calculatorul.
In programarea imperativa clasica, programul este o lunga lista de
instructiuni ce opereaza pe niste date globale.
In PROGRAMAREA STRUCTURATA se cauta imbunatatirea claritatii, calitatii
si a timpului de dezvoltare a codului, impunand ca listele de instructiuni
sa aiba o organizare in subrutine, blocuri si cicluri (in contrast cu
folosirea unor simple teste si salturi, care pot conduce la un "spaghetti
code" dificil de inteles si intretinut).
In PROGRAMAREA PROCEDURALA lista de instructiuni si datele sunt impartite
in mai multe SUBRUTINE (proceduri, functii), avand fiecare o anumita
interfata formata din parametri si valoare returnata, si care pot fi
apelate (cu parametri actuali adecvati) ca si cand ar fi instructiuni
obisnuite (iar atunci se executa codul incorporat, cu parametrii actuali
furnizati); din interiorul unei subrutine putem apela alte subrutine; un
desiderat urmarit in programarea procedurala este MODULARITATEA, prin
care cerem ca instructiunile dintr-o subrutina sa opereze doar pe date
locale subrutinei (care nu sunt accesibile din alte subrutine) iar
comunicarea subrutinei cu apelantii ei sa se efectueze doar prin interfata
acesteia (parametri, valoare returnata) - astfel, codul este mai usor
de intretinut si reutilizat.
- OOP reprezinta conceptele ca "OBIECTE", avand campuri de date (ATRIBUTE
sau PROPRIETATI care descriu obiectul) si proceduri asociate (numite
METODE); obiectele sunt manevrate ca un tot unitar, ca si cand ar fi niste
date obisnuite;
tipul unui obiect s.n. CLASA (obiectele sunt INSTANTE ale claselor);
clasele/obiectele se pot defini unele pe baza altora prin MOSTENIRE,
formand astfel IERARHII; un program este o colectie de clase si obiecte
care interactioneaza si descrie aceasta interactiune.
Astfel, daca in programarea imperativa conventionala programul are
aspectul unei liste lungi de de instructiuni ce opereaza pe niste date
globale, in OOP programul are aspectul unui sistem complex de declaratii

organizate ierarhic, care descriu interactiunile dintre niste obiecte ce


incapsuleaza date si cod.
Exista mai multe variante de OOP:
* dupa separarea domeniilor de interes (separation of concerns principiu de design pentru a separara programul in sectiuni distincte,
fiecare adresand un alt dominiu de interes; un dominiu de interes
(concern) este o informatie ce afecteaza codul unui program, ex: detalii
ale hardware-ului tinta, numele unei clase ce trebuie instantiate, etc.):
** PROGRAMAREA ORIENTATA PE ASPECTE (AOP - aspect-oriented programming):
vizeaza cresterea modularitatii prin aceea ca permite separarea
domeniilor de interes care afecteaza alte domenii de interes
(cross-cutting concerns) - acestea de regula nu pot fi separate de
restul sistemului.
** PROGRAMAREA ORIENTATA PE ROLURI (role-oriented programming): vizeaza
exprimarea lucrurilor in termeni analogi intelegerii conceptuale umane
a lumii; ideea principala este aceea ca oamenii gandesc in termenii
unor roluri, cum ar fi relatiile sociale; de exemplu: un student la
un curs si acelasi student la o petrecere sunt aceeasi persoana, dar
juacand roluri diferite - interactiunea acestei persoane cu lumea
exterioara depinde de rolul sau curent.
** PROGRAMAREA ORIENTATA PE SUBIECTE (subject-Oriented Programming):
starea (campurile) si comportamentul (metodele) unui obiect nu sunt
considerate intrinseci acestuia ci sunt date prin diverse perceptii
subiective ("subiecte") ale sale; de exemplu: noi putem percepe un
copac ca avand drept caracteristici masurabile inaltimea, greutatea,
cantitatea de frunze, etc., in timp ce pentru o pasare el poate avea
de asemenea masuri ale valorilor relative pentru scopuri de hranire si
cuibarit.
* PROGRAMAREA BAZATA PE CLASE: mostenirea este obtinuta definind clase
de obiecte (mostenirea este definita la nivelul claselor); este
cel mai popular si mai dezvoltat model;
exemple de limbaje: C++, Java, ObjectPascal, Turbo Pascal (v5.5+),
Visual Basic.
* PROGRAMAREA BAZATA PE PROTOTIPURI (prototype-based programming):
nu sunt definite clase, iar refolosirea comportamentului (analogul
mostenirii, in cazul claselor) este efectuata prin clonarea obiectelor
existente, care servesc drept prototipuri;
exemple de limbaje: ECMAScript (si implementarile sale JavaScript,
JScript si ActionScript 1.0 pentru platforma Flash), Cecil,
NewtonScript, Io, MOO, REBOL, Lisaac.
- PROGRAMAREA FUNCTIONALA descrie calculul ca evaluarea unor functii
matematice, evitand conceptele de stare si date mutabile (i.e. date ce
pot fi modificate dupa ce au fost create, intr-un mod observabil din
exterior); ea pune in evidenta aplicarea functiilor, in contrast cu
programarea imperativa, care pune in evidenta schimbarile de stare.
Un program este o colectie de functii, care sunt blocuri de cod ce
se doreste a se comporta ca niste functii matematice; rularea programului
se face apeland una din functii, cu niste parametri actuali.
Exemplu: functia lui Fibonacci poate fi scrisa in Common Lisp astfel:
(defun fib (n &optional (a 0) (b 1))
(if (= n 0)
a
(fib (- n 1) b (+ a b))))
atunci, programul poate fi apelat astfel:

(fib 10)
si va afisa:
34
In practica, diferenta dintre o functie matematica si notiunea de functie
folosita in programarea imperativa este ca functiile imperative pot avea
efecte laterale (side effects) ce pot schimba starea programului; de aceea,
ele nu au transparenta referentiala (o expresie are transparenta
referentiala daca poate fi inlocuita in cod cu valoarea ei fara sa se
schimbe comportamentul programului), deci o expresie poate produce valori
diferite la momente diferite, pentru aceleasi argumente, in functie de
starea de executie a programului. In programarea functionala valoarea
furnizata de o functie depinde doar de argumentele acesteia (deci, daca
o vom apela de mai multe ori cu aceleasi argumente, va furniza acelasi
rezultat); comportamentul programului este astfel mai usor de inteles si
mai predictibil.
- PROGRAMAREA LOGICA utilizeaza instructiuni logice pentru a descrie un
calcul sau modul in care rezolvarea unei probleme se reduce la rezolvarea
altor probleme; ea poate fi privita ca o forma de programare declarativa,
dar si procedurala.
Un exemplu de instructiune logica este clauza Horn; de exemplu:
p(X, Y) if q(X) and r(Y)
poate semnifica "q(X) si r(Y) implica p(X, Y)" sau "pentru a rezolva
p(X, Y), intai rezolvam q(X) si apoi r(Y)".
In general, calculul este privit ca un rationament automatizat (automated
reasoning) asupra unui corpus de cunostinte; ipotezele asupra domeniului
problemei sunt exprimate prin formule logice(scrise ca instructiuni logice)
si formeaza programul, iar rularea acestuia consta in aplicarea de reguli
de inferenta (deductie) asupra lor pana este gasit un raspuns la problema
sau colectia de formule se dovedeste inconsistenta (contradictorie).
Exemplu: program Fibonacci recursiv, in Prolog:
fib(0, 0).
fib(1, 1).
fib(X, Y) :X > 1,
X2 is X
2, fib(X2, Y2),
X1 is X 1, fib(X1, Y1),
Y is Y1 + Y2.
atunci, programul poate fi apelat astfel:
fib(10, 34).
si va afisa:
yes
sau astfel:
fib(10, 35).

si va afisa:
no
- PROGRAMAREA DECLARATIVA descrie logica unui calcul fara a descrie fluxul
ei de control (ordinea in care operatiile sunt executate); ea exprima "ce"
trebuie sa faca programul, fara a prescrie "cum" sa o faca, in termenii
unor secvente de actiuni ce trebuie efectuate. Programarea functionala si
cea logica sunt considerate forme de programare declarativa.
- PROGRAMAREA GENERICA este un stil de programare in care algoritmii sunt
definiti deasupra unor tipuri ce urmeaza a fi precizati mai tarziu si sunt
instantiati ulterior, atunci cand este nevoie de ei, pentru niste tipuri
particulare date ca parametri; astfel, daca mai multe fragmente de cod
difera doar prin tipurile de date asupra carora opereaza, ele nu mai
trebuie scrise separat, ci doar o singura data, generic (deasupra unor
tipuri date ca parametri) - se evita astfel fenomenul de DUPLICARE A
CODULUI (duplicate code), adica aparitia de mai multe ori a unei secvente
de cod in cadrul unui program sau al unui grup de programe ce apartin sau
sunt intretinute de o aceeasi entitate.
Aceasta paradigma poate fi adaugata unor limbaje imperative, ex: Ada,
Delphi, C++, Java, C#, Visual Basic .NET, dar si unor limbaje functionale,
ex: Haskell, Clean.
Multe paradigme ale programarii sunt la fel de bine cunoscute pentru
tehnicile pe care le interzic, ca si pentru cele pe care le introduc.
De exemplu, programarea functionala interzice utilizarea efectelor
laterale si schimbarea valorii variabilelor prin atribuire (propunand
folosirea in schimb a recursiei), iar programarea structurata interzice
folosirea instructiunii "goto". Interzicerea anumitor tehnici poate fi
perceputa ca o constrangere de catre programatori, dar face programele mai
usor de inteles si inlesneste demonstrarea unor teoreme privind
proprietatile acestora.
Unele limbaje de programare suporta mai multe paradigme. De exemplu un
program in Object Pascal sau C++ poate fi pur procedural, sau pur orientat
pe obiecte, sau continand elemente ale ambelor paradigme.
Exemple de limbaje de programare:
- BASIC, FORTRAN, COBOL: imperative, procedurale;
- C, Pascal: imperative, structurate, procedurale;
- C++, Java, Object Pascal, Turbo Pascal (v5.5+), Visual BASIC: imperative,
structurate, procedurale, OOP;
- Lisp: familie de limbaje functionale: Common Lisp, Scheme; CommonLOOPS
este o forma de Lisp orientata pe obiecte;
- Prolog, Datalog: limbaje de programare logica declarativa, bazata pe
clauze Horn;
OBJ, CafeOBJ, Maude: limbaje de programare logica declarativa, bazate pe
logica ecuationala; Full Maude este o extensie a lui Maude orientata
pe obiecte;
SQL: limbaj de interogare baze de date; SQL1999 (cunoscut anterior ca
SQL3) este un SQL orientat pe obiecte.
Evolutia limbajelor de programare a avut urmatoarele etape:

- la inceput, cand au fost inventate calculatoarele, se folosea CODUL


MASINA, in care instructiunile, datele, adresele sunt reprezentate exact
asa cum apar ele memorie, ca secvente de numere; de obicei, programarea
se facea introducand instructiunile masina in cod binar, de la panoul
frontal al calculatorului; acest mod de lucru a fost convenabil cat timp
programele aveau doar cateva sute de instructiuni;
- o data cu marirea programelor, au fost inventate LIMBAJELE DE ASAMBLARE,
care folosesc o reprezentare simbolica a instructiunilor masina (prin
mnemonice) si a adreselor de memorie (prin etichete); astfel, puteau fi
scrise mai usor programe de dimensiuni mai mari;
- deoarece programele continuau sa creasca, au fost introduse LIMBAJELE DE
NIVEL INALT (numite uneori si LIMBAJE PROCEDURALE), care lucreaza cu
cocepte si operatii abstracte (independente de o anumita implementare) si
au instructiuni puternice, inclusiv lucrul cu subrutine, care ofera un
plus e expresivitate; primul dintre ele a fost FORTRAN (anii '50);
- in anii '60 a fost introdusa PROGRAMAREA STRUCTURATA (limbaje ca Pascal,
C), care permite gestionarea mai usoara a unui cod de mari dimensiuni si
inlesneste demonstrarea unor teoreme privind proprietatile acestora
(ex: teorema Bohm-Jacopini, logica lui Hoare); limbajul C s-a dovedit
foarte util pentru scrierea de cod performant, dar daca dimensiunea
unui program trece de la 25000 la 100000 linii el devine dificil de
stapanit ca un intreg;
- conceptele OOP au inceput sa fie introduse pe la sfarsitul anilor '50 si
inceputul anilor '60, si au capacitatea de a creste foarte mult
expresivitatea limbajului, a.i. sa poate fi manevrate usor programe si
mai mari; fiind vorba de o paradigma, nu de un limbaj, OOP a fost
aplicat nu numai limbajelor imperative (Simula67, Object Pascal, C++) ci
si celor functionale (CLOS, derivat din Lisp), logice (Full Maude), etc.,
chiar si limbajelor de asamblare (HLA);
- programarea declarativa (in particular programarea functionala si cea
logica) a fost dezvoltata independent de ramura programarii imperative;
anumite concepte, de exemplu OOP, au fost aplicate insa si aici.
II. Caracteristicile OOP
========================
In ceea ce priveste trasaturile fundamentale ale OOP, exista mai multe
puncte de vedere:
Herbert Schildt, in "C++ The Complete Reference" (versiunea in limba
romana este mentionata in bibliografie), evidentiaza trei caracteristici:
- INCAPSULAREA (encapsulation):
Ca si concept de programare, presupune doua aspecte:
* o constructie de limbaj ce faciliteaza impachetarea datelor cu
operatiile definite asupra lor (sub forma de metode sau alte functii)
* un mecanism de limbaj pentru restrictionarea accesului la unele
componente ale pachetului.
In C++ codul si datele sunt impachetate in OBIECTE, creandu-se legaturi
stranse intre ele; obiectul este apoi manevrat ca un tot unitar, ca si cand
ar fi o data obisnuita (putem avea variabile cu valori obiecte). Codul

si datele cuprinse intr-un obiect nu sunt accesibile in totalitate din


afara in mod direct (sunt ascunse, ca PRIVATE), prevenindu-se astfel
accesul incorect sau neutorizat la ele; pentru accesare, obiectul ofera o
interfata formata din anumite metode ale sale (considerate PUBLICE) ce pot
fi apelate de catre alte clase/obiecte si prin care acestea pot invoca
functionalitatea obiectului.
De obicei campurile de date sunt private, iar accesul la ele din exterior
este permis prin metode publice gen "setter" si "getter".
TODO: exemplu de incapsulare in C++;
- POLIMORFISMUL (polymorphism):
Poate fi descris pe scurt astfel: "o singura interfata, metode multiple";
Este un mecanism general prin care o aceeasi sintagma ce descrie o
prelucrare (de exemplu o expresie) va avea automat efecte diferite in
functie de tipul operanzilor.
In C++ exista mai multe modalitati de a obtine polimorfismul, una dintre
ele este SUPRAINCARCAREA functiilor si operatorilor; de exemplu, putem
defini un operator "+" pentru matrici, care sa insemne adunarea matricilor
si un operator "+" pentru stringuri, care sa insemne concatenarea
stringurilor; cei doi operatori "+" se pot defini ca niste functii;
atunci, la evaluarea expresiei "a+b" se va apela automat functia de
adunare, daca a si b sunt obiecte-matrice, sau functia de concatenare,
daca a si b sunt obiecte-string; in C ar fi trebuit sa avem nume diferite
pentru functii diferite.
In exemplul anterior, interfata este data de operatorul "+", iar metodele
date de cele doua functii (adunare, concatenare). La intalnirea expresiei
"a+b", functia ce va fi apelata poate fi decisa la momentul compilarii
(compilatorul face alegerea in functie de tipurile lui a si b si genereaza
o instructiune de apel catre adresa functiei respective, deci o adresa
fixata prin cod) sau la momentul rularii (compilatorul genereaza un cod
care prin executare determina adresa dorita in functie de valorile curente
ale lui a si b si o instructiune de apel catre adresa rezultata, deci o
adresa determinata la momentul rularii); primul mod de asociere s.n.
"EARLY BINDING", al doilea "LATE BINDING" si conduc la un POLIMORFISM
LA MOMENTUL COMPILARII, respectiv la un POLIMORFISM LA MOMENTUL RULARII.
Un caz particular de polimorfism este POLIMORFISMUL DE SUBTIP (subtype
polymorphism): este un mecanism prin care un tip de date este asociat ca
subtip unui alt tip de date in sensul unei notiuni de substitutabilitate,
insemnand ca elemente ale programului, de obicei subrutine (proceduri,
functii) scrise sa opereze cu elemente ale supertipului vor putea opera
automat si cu elemenente ale subtipului. In C++ acest mecanism se aplica
claselor si obiectelor obtinute prin mostenire.
- MOSTENIREA (inheritance):
In OOP, mostenirea este un mod de a refolosi codul unor obiecte existente,
si/sau de a stabili un subtip dintr-un obiect existent.
In PROGRAMAREA BAZATA PE CLASE, unde obiectele sunt definite prin clase,
clasele pot mosteni atribute si comportamente ale unor clase
pre-existente, numite CLASE DE BAZA (sau SUPERCLASE, CLASE PARINTE);
clasele rezultate s.n. CLASE DERIVATE (sau SUBCLASE, CLASE COPIL); relatia
dintre clase determinata de mostenire defineste o IERARHIE.
In C++, la definirea unei clase derivate se vor specifica doar clasele de

baza pe care le mosteneste si atributele/metodele noi adaugate; clasa


derivata va incorpora automat si atributele/metodele claselor de baza si
fi subclasa a acestora, in sensul polimorfismului de subtip.
TODO: exemplu de mostenire in C++
In PROGRAMAREA BAZATA PE PROTOTIPURI obiectele pot fi definite direct din
alte obiecte, fara a fi nevoie sa definim clase; in acest caz avem de a
face cu o MOSTENIRE DIFERENTIALA (differential inheritance).
Benjamin C. Pierce, in "Types and Programming Languages", MIT Press,
2002, evidentiaza urmatoarele caracteristici ale OOP:
- ASOCIEREA DINAMICA (dynamic dispatch): cand o metoda este apelata pentru
un obiect, obiectul insusi determina ce cod se va executa, cautand metoda
la momentul rularii (run time) intr-o tabela asociata cu obiectul; este
un concept de late binding (a se vedea mai sus);
- Incapsularea;
- Polimorfismul de subtip;
- Mostenirea;
- RECURSIA DESCHISA (open recursion): existenta unei variabile speciale
(numita de obicei "this" (ex. in C++) sau "self" (ex. in Visual BASIC),
denumirea sa poate fi un cuvant-cheie) ce permite ca dintr-o metoda a
unui obiect sa poata fi apelata o alta metoda a aceluiasi obiect;
asocierea se face la run time (variabila este late-bound), a.i. permite
ca o metoda definita intr-o clasa sa apeleze o alta metoda definita
ulterior, intr-o subclasa.
John C. Mitchell, in "Concepts in programming languages", Cambridge
University Press, 2003, evidentiaza urmatoarele caracteristici ale OOP:
- Asocierea dinamica;
- ABSTRACTIZAREA (abstraction): este mecanismul prin care datele si
programele sunt definite cu o reprezentare similara in forma cu intelesul
(semantica) lor, ascunzand in acelasi timp detaliile de implementare;
cu alte cuvinte, forma in care scriem operatiile reflecta semnificatia pe
care o au pentru noi si nu mecanismul prin care sunt ele efectuate.
Un nivel de abstractizare inferior expune programatorului detaliile
hardware-ului unde este rulat programul, in timp ce un nivel de
abstractizare superior ascunde aceste detalii si se ocupa doar cu
logica de business a programului ("logica de business" este un termen
non-tehnic folosit pentru a descrie algoritmii functionali ce manevreaza
transferul de informatie intre o baza de date si o interfata utilizator).
- Polimorfismul de subtip;
- Mostenirea.
Michael Lee Scott in "Programming language pragmatics", Edition 2, Morgan
Kaufmann, 2006, considera urmatoarele caracteristici ale OOP:
- Incapsularea;
- Mostenirea;

- Asocierea dinamica.
In ceea ce priveste stilul de programare intr-un limbaj obiectual, acesta
difera de cel specific programarii imperative procedurale clasice - de
exemplu sa facem o comparatie C versus C++:
In C programatorul priveste prelucrarea din interior, avand o perceptie
dinamica, operativa, a acesteia, si o exprima explicitand seria de operatii
ce trebuie efectuate sub forma unei lungi liste de instructiuni, impartita
eventual in mai multe functii; programul are o dezvoltare pe orizontala,
avand aspectul unei colectii de functii, care contin liste lungi de
instructiuni.
In C++ programatorul priveste prelucrarea din exterior, avand o perceptie
statica, descriptiva, a acesteia, si o exprima descriind legaturile logice
(dependentele) intre operatiile ce trebuie efectuate, sub forma unui sistem
complex de declaratii; programul are o dezvoltare pe verticala, avand
aspectul unei ierarhii de clase si obiecte, care reutilizeaza extensiv cod
mostenit (metodele noi adaugate contin de fiecare data cod putin).
Am putea face o paralela intre modul in care definim un sir prin formula
termenului general sau prin recurenta.
Cand folosim formula termenului general suntem in C: scriem o expresie
comlpexa care expliciteaza toate operatiile ce trebuie efectuate pentru
calcularea termenului curent.
Cand folosim formula de recurenta, suntem in C++: scriem o expresie mult
mai simpla care descrie logica dependentei dintre termenul curent si cativa
termeni anteriori (declaratie prin mostenire); expresia simpla contine
aceeasi informatie ca si cea complexa de mai inainte, dar nu o expliciteaza
- toate calculele din expresia complexa se vor generea si se vor executa
la run time; astfel, putem descrie aceleasi calcule cu cod mai putin
(creste expresivitatea limbajului).
Evident, deoarece C++ extinde C, putem programa in C++ in stil C (folosind
functii independente, care nu sunt metode ale obiectelor, punand cod mult
in metode putine si dezvoltand programul pe orizontala, etc.) dar atunci nu
vom putea beneficia de toate avantajele acestui limbaj. Diferenta se va
simti mai ales la programele de dimensiuni mari, unde avem nevoie de o
expresivitate a limbajului si o productivitate a programarii sporite.

In continuare, vom studia conceptele OOP pe cazul particular al C++.


III. Istoria C++
================
C++ a fost dezvoltat de Bjarne Stroustrup de la Bell Labs incepand cu 1979
(initial s-a numit C cu clase).
El este un limbaj de programare care extinde limbajul C si este:
- de uz general (general-purpose);
- de nivel intermediar (intermediate-level language): combina atat
trasatauri ale unui limbaj de nivel inalt (high-level, i.e. cu un nivel

puternic de abstractizare fata de detaliile calculatorului) cat si ale


unuia de nivel scazut (low-level);
- compilat: implementarile sale sunt in mod tipic compilatoare
(translatoare ce genereaza cod masina din cod sursa) si nu
interpretoare (executoare pas-cu-pas ale codului sursa, neavand loc
translatari inaintea momentului rularii);
- free-form: pozitionarea caracterelor in pagina codului sursa este
insignifianta;
- cu verificarea static a tipulurilor (statically typed): verificarea
tipurilor se face la momentul compilarii; in opozitie, la un limbaj cu
verificarea dinamica a tipurilor (dynamic typed) cea mai mare parte a
verificarii tipurilor se face la momentul rularii (ex: JavaScript, Lisp,
Perl (pentru tipurile definite de utilizatori, nu cele built-in), Prolog,
Python, Ruby, etc.);
- multi-paradigm ; este un limbaj de programare imperativa ce suport
programarea structurata, procedural , abstractizarea datelor, programarea
orientat pe obiecte, programarea generica.
Filozofia C++ este descrisa de B. Stroustrup in "The Design and Evolution
of C++" (1994) prin urmatoarele reguli pe care s-a bazat cand a poiectat
limbajul:
- C++ este proiectat a.i. sa fie un limbaj statically typed,
general-purpose, care sa fie eficient si portabil ca si limbajul C;
- C++ este proiectat pentru a suporta direct si comprehensiv mai
multe paradigme (programarea procedurala, abstractizarea datelor,
programarea orientata pe obiecte si programarea generica);
- C++ este proiectat sa ofere programatorului libertatea de a alege,
chiar daca aceasta permite ca programatorul sa aleaga incorect;
- C++ este proiectat sa fie compatibil cu C cat mai mult posibil, a.i.
sa ofere o tranzitie lina de la C;
- C++ evita elemente care sunt specifice unei anumite platforme sau nu
sunt de uz general;
- C++ nu accentueaza elementele care nu sunt folosite (principiul
"zero-overhead");
- C++ este proiectat sa functioneze fara a un mediu de programare
sofisticat.
Per ansamblu, C++ combina adecvarea la productia de software performant pe
care o are limbajul C cu expresivitatea ridicata pe care o aduce orientarea
pe obiecte (expresivitate ridicata inseamna ca cu cod putin poti sa descrii
mult).
Domeniile sale de aplicare includ software de sistem, software de
aplicate, drivere de echipament (device drivers), software incorporat
(embedded software), aplicatii client si server de inalta performanta,
software de entertainment (ex: jocuri video). C++ este folosit inclusiv
pentru proiectarea hardware (hardware design).
Istoricul C++:

- B. Stroustrup a inceput sa lucreze la C cu clase in 1979; el a constatat


ca Simula avea facilitati foarte utile pentru proiecte mari, insa era prea
lent, in timp ce BCPL era rapid, insa nu era de nivel inalt si era
nepotrivit pentru proiecte mari; amintindu-si de experienta sa din perioada
lucrarii de doctorat, Stroustrup a inceput sa imbunatateasca C cu facilitati
asemanatoare Simula (C a fost ales deoarece era de uz general, rapid,
portabil si folosit pe scara larga).
La inceput facilitatile adaugate C-ului au fost clase, clase derivate,
verificare puternica a tipului (strong typing - un sistem de tipuri este
astfel daca specifica una sau mai multe restrictii asupra modului in care
operatiile care implica valori de diferite tipuri pot fi intermixate),
expansiunea inline (inlocuirea unui apel de functie direct cu o copie
adaptata a corpului acesteia, eliminandu-se instructiunile de apel si
revenire) si argumentele cu valori implicite (default arguments).
In timp ce a proiectat C cu clase (mai apoi C++), Stroustrup a scris si
Cfront, un compilator care genera cod sursa C din cod C cu clase. Prima
lansare comerciala a fost in 1985.
- In 1983, numele limbajului a fost schimbat din C cu clase in C++
("++" fiind operatorul de incrementare din C).
Au fost adaugate noi facilitati, inclusiv functii virtuale,
supraincarcarea functiilor si a operatorilor, referinte, constante,
alocare dinamica, un control al tipului imbnatatit si noua varianta de
comentariu pe un singur rand (comentariile care incep cu caracterele '//').
- In 1985 Stroustrup a publicat prima editie a cartii sale "The C++
Programming Language" (Limbajul de programare C++), oferind informatii
importante despre limbaj, care inca nu era un standard oficial.
- In 1989 a fost lansata versiunea 2.0 a C++, iar editia a doua,
actualizata, a cartii "The C++ Programming Language" a fost publicata in
1991; au fost adaugate mostenirea multipla, clase abstracte, functii
membre statice, functii membre constante si membri protejati (protected).
In 1990 a fost publicata lucrarea "The Annotated C++ Reference Manual",
care a devenit baza standardizarii ulterioare; ultimele adaugiri includeau
sabloane (template), exceptii, spatii de nume (namespace), noi
conversii (cast) si tipul boolean.
- O data cu evolutia limbajului C++, a evoluat si biblioteca sa standard.
Prima adaugire a fost biblioteca de intrari/iesiri stream (I/O stream),
care oferea facilitati pentru a inlocui functiile traditionale C cum ar fi
printf si scanf; mai tarziu, printre cele mai semnificative adaugiri la
biblioteca standard a fost STL (Standard Template Library) (Biblioteca de
sabloane standard).
- Dupa ani de lucru, un comitet ANSI-ISO a standardizat C++ in 1998
(ISO/IEC 14882:1998).
Evolutia standardelor C++ a fost:
Anul
C++ Standard
Nume informal
--------------------------------------------------------1998
ISO/IEC 14882:1998
C++98
2003
ISO/IEC 14882:2003
C++03
2007
ISO/IEC TR 19768:2007
C++TR1
(TR1 este un raport tehnic care nu este un standard in sine,
propunea adaugir la biblioteca standard C++ si a circulat
ca draft din 2005)
2011
ISO/IEC 14882:2011
C++11
La ora actuala (2013) versiunea curenta a standardului este C++11,

si se afla in pregatire C++14 (o revizie minora) si C++17 (o revizie


majora).
In anii 1990, C++ a devenit unul din cele mai populare limbaje de programare
comerciale.
Mai multe grupuri ofera compilatoare C++ atat free cat si proprietare;
exemple: GNU Project, Microsoft, Intel si Embarcadero Technologies.
Lectia 2 - Incursiune in C++ : TODO
============================
Caracteristicile limbajului C++ sunt in mare masura corelate intre ele,
ceea ce face dificil de descris o caracteristica fara implicarea multor
altora, presupunand astfel existenta unor cunostinte anterioare despre ele.
De aceea, in aceasta lectie vom face o prezentarea sumara a principalelor
caracteristici C++, pentru a ne putea referi la ele cand va fi nevoie.
Urmatoarele lectii vor prezenta caracteristicile C++ in detaliu.
Lectia se doreste a fi totodata si un scurt tutorial de C++, care sa
permita abordarea cat mai devreme a problemelor de laborator.
In tot acest curs vom presupune cunoscut limbajul C si vom insista mai
ales pe evidentierea elementelor noi pe care le aduce C++.
Lectia 3 - Incompatibilitati intre C si C++
===========================================
Deoarece C++ s-a dezvoltat din C si a fost proiectat sa fie source-and-link
compatibil with C. Din acest motiv, istrumentele de dezvoltare pentru cele
doua limbaje (cum ar fi IDE-urile sau compilatoarele) sunt adesea integrate
intr-un singur produs, programatorul avand posibilitatea de a specifica
limbajul sursa ca fiind C sau C++ (de ex. prin extensia fisierului sursa:
.c pentru C si .cpp pentru C++). Totusi, din cauza unor diferente semantice
minore, anumite constructii C nu sunt acceptate in C++ sau au sensuri
diferite. De aceea, C++ nu este un superset al lui C in sens strict.
Bjarne Stroustrup a sugerat ca incompatibilitatile dintre C si C++ sa fie
reduse la minimum in scopul de a maximiza inter-operabilitatea intre cele
doua limbaje, iar argumentarea oficiala a standardului C din 1999 (C99)
sustinea principiul mentinerii celui mai larg subset comun intre C si C++,
mentinand in acelasi timp o distinctie intre ele si permitandu-le sa
evolueze separat, si afirma ca autorii erau multumiti sa lase C++ sa fie
limbajul cel "mare si ambitios".
La ora actuala (2013) versiunea curenta a standardului este C++11
(ISO/IEC 14882:2011), iar cea a standardului C este C11 (fost C1X)
(ISO/IEC 9899:2011).
In continuare prezentam cateva incompatibilitati intre C si C++; au fost
luate in considerare versiunile C99 (ISO/IEC 9899:1999) si C++98
(ISO/IEC 14882:1998), a se vedea:
David R. Tribble: "Incompatibilities Between ISO C and ISO C++",
http://david.tribble.com/text/cdiffs.htm
Unele dintre incompatibilitatile discutate nu se manifesta la fel pe toate
compilatoarele, asa cum am semnalat in notele inserate in text.

I. Constructii valide in C, dar nu si in C++


============================================
(1) C permite ca un void* sa fie asignat oricarui tip pointer fara cast,
in timp ce C++ nu permite acest lucru.
O asemenea situatie este adesea intalnita in secventele de cod C ce
folosesc alocarea dinamica de memorie cu malloc; exemple de cod valid in C
dar nu si in C++:
void* ptr;
int *i = ptr;

/* Conversie implicita de la void* la int* */

si de asemenea:
int *j = malloc(sizeof(int) * 5);

/* Conversie implicita de la
void* la int* */

Pentru a face fragmentele de cod de mai sus compilabile in C++, trebuie


folosit un cast explicit:
void* ptr;
int *i = (int *) ptr;
si respectiv:
int *j = (int *) malloc(sizeof(int) * 5);
De notat insa ca in ambele limbaje orice tip pointer poate fi asignat unui
void* fara cast; acest lucru permite compilarea in C++ fara modificari a
secventelor de cod C care contin apeluri ale unor functii de biblioteca
pentru manevrarea blocurilor de memorie, de exemplu:
#include <string.h>
int a[]={1,2,3}, b[3];
memcpy(b,a,sizeof(a));
(functia memcpy are prototipul:
void *memcpy(void *dest, const void *src, size_t n);
iar apelul de mai sus presupune ca un int* sa fie asignat unui const void*).
(2) C++ are mai multe cuvinte cheie in plus fata de C, ceea ce face ca
secventele de cod C care le folosesc drept identificatori sa fie invalide
in C++; de exemplu:
struct template
{
int new;
struct template* class;
};
este un cod valid in C, dar invalid in C++, deoarece "template", "new" si
"class" sunt cuvinte cheie.
(3) Compilatoarele de C++ interzic utilizarea unui goto sau switch care
traverseaza o initializare, ca in urmatorul fragment de cod (valid in C99):

void fn(void)
{
goto eticheta;
int i = 1;
eticheta:
;
}
De notat ca intercalarea declaratiilor de variabile cu instructiuni
(ca in exemplul de mai sus) nu era permisa la primele versiuni de C
(acolo declaratiile de variabile trebuiau puse la inceputul blocului iar
instructiunile la sfarsitul sau), dar in C99 este permisa.
(4) Daca operandul drept al operatorului virgula este o "l-value" (entitate
ce poate fi folosita in stanga unei atribuiri), atunci operatorul va
produce o "l-value" in C++ si doar o "r-value" (entitate ce poate fi
folosita in dreapta unei atribuiri) in C; exemplu:
int i, j, x, y;
i = 1; j = 2; x = 3; y = 4;
x = (i, j); /* valid si in C si in C++; x primeste valoarea 2 */
(i, j) = y; /* valid in C++ (j primeste valoarea 4), invalid in C */
(5) C nu permite duplicarea unui typedef in acelasi domeniu (scope), in timp
ce C++ permite typedef-uri repetate;
scope = context de includere unde sunt sunt asociate valori si expresii
(enclosing context where values and expressions are associated);
exemplu:
typedef int MyInt;
typedef int MyInt;

/* valid in C++, invalid in C */

De aceea, typedef-urile care ar putea fi incluse mai mult decat o data


intr-un program (de ex. typedef-uri uzuale care apar in mai multe fisiere
header) trebuie protejate prin directive de preprocesare, daca un asemenea
cod sursa urmeaza a fi compilat atat in C cat si in C++; de exemplu:
/* Fisierul one.h */
#ifndef MYINT_T
#define MYINT_T
typedef int MyInt;
#endif
...
/* Fisierul two.h */
#ifndef MYINT_T
#define MYINT_T
typedef int MyInt;
#endif
...
atunci o secventa de cod poate include ambele fisiere header fara sa
provoace o eroare in C:

/* Include fisiere header multiple care definesc typedef MyInt */


#include "one.h"
#include "two.h"
MyInt

my_counter = 0;

(6) Constantele tipurilor enumerare (enum values) in C sunt intotdeauna de


tip int, in timp ce in C++ pot fi de tipuri diferite de int si pot avea
dimensiuni diferite de cea a lui int.
Mai exact, in C constantele tipurilor enumerare sunt doar constante cu
nume de tip signed int; in consecinta, ele nu pot avea valori de
initializare decat in intervalul [INT_MIN,INT_MAX] iar sizeof-ul lor este
egal cu sizeof(int).
In C++ compilatoarele pot implementa tipurile enumerare folosind o gama
mai larga de tipuri intregi (signed int, unsigned int, signed long,
unsigned long); constantele tipurilor enumerare au acelasi tip ca si tipul
lor enumerare, deci aceeasi dimensiune (sizeof) si aliniere ca si tipul
intreg subiacent, astfel incat dimensiunea (sizeof) lor si intervalul
valorilor de initializare ce pot fi folosite nu sunt neaparat cele ale
lui int.
Aceasta poate provoca incompatibilitati la compilarea unui cod C in C++,
daca compilatorul de C++ alege sa implementeze un tip enumerare cu o
dimensiune (sizeof) diferita decat ar fi fost in C, sau daca rezultatul
programului se bazeaza pe rezultatele unor expresii ca sizeof(RED) (unde
RED este o constanta a unui tip enumerare); exemplu:
enum ControlBits
{
CB_LOAD = 0x0001,
CB_STORE = 0x0002,
...
CB_TRACE = LONG_MAX+1,
CB_ALL =
ULONG_MAX
};

/* Comportament nedefinit */

(7) Identificatorii C++ nu pot contine doi sau mai multi underscore
(caracterul "_") consecutivi, in orice pozitie; identificatorii C nu pot
incepe cu doi sau mai multi underscore consecutivi, dar ii pot contine in
alte pozitii.
Nota: cateva teste efectuate cu compilatoarele gcc si g++ versiunea
4.5.1 20101208 nu au raportat erori la folosirea identificatorilor
de forma mentionata.
(8) In fisierele header ale bibliotecii standard C++ unele functii din
biblioteca standard C au declaratiile modificate pentru a fi mai sigure din
punct de vedere al tipurilor (type-safe) atunci cand sunt folosite in C++;
de exemplu sunt adaugati calificatori const suplimentari.
Astfel, declaratia functiei din biblioteca standard C:
/* <string.h> */
extern char * strchr(const char *s, int c);
este inlocuita in biblioteca C++ cu urmatoarele declaratii:
/* <cstring> */

extern const char * strchr(const char *s, int c);


extern char *
strchr(char *s, int c);
avem deci doua functii strchr prin supraincarcare (overload), iar la apelul
strchr cu niste parametri actuali compilatorul va alege una dintre cele doua
functii in concordanta cu tipurile parametrilor actuali (a se vedea lectia 4).
Aceste declaratii usor diferite pot provoca probleme atunci cand un cod C
este compilat in C++, de exemplu:
/* cod C */
const char * s = "abcde";
char *
p;
p = strchr(s, 'd');

/* valid in C, invalid in C++ */

intr-adevar, deoarece primul parametru actual este de tip const char *,


si nu char *, compilatorul va alege prima functie strchr; aceasta returneaza
insa valori const char *, si nu char *, care sunt incompatibile cu p (deoarece
p este char *) - nu este permisa asignarea unui pointer constant unei
variabile non-const.
Codul de mai sus se poate corecta insa printr-un simplu cast, devenind valid
atat in C cat si in C++:
/* Corectie pentru C++ */
p = (char *) strchr(s, 'd');

/* valid in C si in C++ */

(9) Atat in C cat si in C++ putem declara tipuri structura incuibate (nested)
in alte structuri, dar domeniul numelui lor (scope) este interpretat diferit:
in C el se extinde si in afara domeniului structurii exterioare, in C++ el
este definit doar in domeniul (scope)/spatiul de nume (namespace) structurii
exterioare.
In general, in C++ declaratiile de structura sau clasa au propriul lor
domeniu (scope), ceea ce nu este valabil si in C; aceasta afecteaza toate
tipurile structura, uniune, enumerare sau alte clase declarate in interior.
Exemplu:
struct Outer
{
struct Inner
/* declaratie de structura incuibata */
{
int
a;
float
f;
}
in;
enum E
/* declaratie de tip enumerare incuibata */
{
UKNOWN, OFF, ON
}
state;
};
struct Inner

si;

/* tipul incuibat este vizibil in C,


nu este vizibil in C++ */

enum E

et;

/* tipul incuibat este vizibil in C,


nu este vizibil in C++ */

Pentru a fi vizibile in C++, declaratiile interioare trebuie numite


explicit folosind ca prefix structura lor exterioara:

/* cod C++ */
Outer::Inner
Outer::E

si;
et;

/* nume de tip explicit */


/* nume de tip explicit */

sau puse in afara structurii exterioare (a.i. sa aiba domeniul (scope) la


nivel de fisier):
/* cod C si C++ */
struct Inner
{
int
float
};

/* declaratia nu mai este incuibata */


a;
f;

enum E
{
UKNOWN, OFF, ON
};
struct Outer
{
struct Inner
enum E
};

/* declaratia nu mai este incuibata */

in;
state;

(10) Definitiile de functii non-prototip ("K&R"-style) nu sunt permise in C++,


iar in C sunt permise, desi sunt considerate invechite (deprecated) din 1990;
de exemplu:
int foo(a, b)
/* sintaxa invechita in C, interzisa in C++ */
int a;
int b;
{
return (a + b);
}
C++ permite doar definitii de functii prototipate, deci pentru a se compila
in C++ codul de mai sus trebuie rescris in forma cu protoip de functie:
int foo(int a, int b)
{
return (a + b);
}
Similar, declaratiile implicite
fost declarate anterior) nu sunt
in versiunile mai vechi, ex: C90
functii sunt declarate implicit,
implicit ca returneaza o valoare

de functii (utilizarea de functii ce nu au


permise in C++, iar in C sunt permise doar
(incepand cu C99 sunt interzise); asemenea
in locul primului lor apel, considerand
int.

De exemplu, urmatorul cod este valid in C90, dar nu si in C99 sau in C++:
/* Nu exista o declaratie anterioara a lui bar()
in domeniul (scope) curent */

void foo(void)
{
bar(); /* declaratie implicita: extern int bar() */
}
De obicei, compilatoarele (vechi) de C, la intalnirea unei functii
nedeclarate anterior, presupun (implicit) un prototip in care tipurile
parametrilor formali coincid cu cele ale parametrilor actuali furnizati
(determinati cu ajutorul regulii conversiilor implicite folosite la
evaluarea expresiilor) iar tipul returnat este int; ca atare, apelantul va
incarca pe stiva valorile parametrilor actuali si va recupera la revenire
valoarea returnata in concordanta cu aceste tipuri; cand se face saltul
in corpul functiei apelate, aceasta va consulta stiva si va returna o
valoare in concordanta cu tipurile folosite la definirea ei (ea poate fi
definita cu antet si corp in acelasi fisier, dar sub apelul respectiv,
sau in alt fisier linkeditat cu cel in care se afla apelul), iar daca
aceste tipuri difera, valorile vor fi comunicate alterat.
TODO: exemplu
(11) Declararea tipurilor struct, union si enum in prototipurile functiilor
este permisa in C, nu si in C++; exemplu:
/* cod valid in C, invalid in C++ */
extern void foo(const struct info { int typ; int sz; } *s);
int bar(struct point { int x, y; } pt)
{ ... }
De asemenea, declararea tipurilor structura ca tipuri returnate de functii
este permisa in C, nu si in C++; exemplu:
/* cod valid in C, invalid in C++ */
extern struct pt { int x; } pos(void);
Motivul pentru care C++ nu permite declaratiile de mai sus este ca domeniul
(scope) unei structuri declarate astfel nu se extinde in afara declaratiei
sau definitiei functiei, facand imposibila definirea de obiecte de acel
tip structura care ar putea fi pasate ca parametri functiei sau asignarea
valorilor returnate de functie cu obiecte de tipul respectiv.
Atat C cat si C++ permit declararea tipurilor structura incomplete in
prototipurile functiilor si ca tipuri returnate de functie:
void frob(struct sym *s); // OK, pointer catre tip incomplet
struct typ * fraz(void); // idem
Nota: teste efectuate cu compilatoarele gcc si g++ versiunea 4.5.1 20101208
au raportat urmatoarele:
- in cazul functiilor foo, bar si frob, gcc afisaza un warning de tipul:
warning: 'struct info'/'struct point'/'struct sym' declared inside
parameter list; its scope is only this definition or declaration,
which is probably not what you want
in cazul functiilor pos si fraz nu semnaleaza warning sau error, iar in
main se pot defini variabile de tip struct pt si struct typ *;
- g++ semnaleaza eroare in cazul functiilor foo, bar, pos (si accepta
frob si fraz).

(12) O declaratie struct, union sau enum introduce un typedef implicit al


aceluiasi nume in C++, dar nu si in C (in C la fiecare invocare a numelui
trebuie sa folosim cuvantul cheie struct, union, sau respectiv enum).
Exemplu:
/* valid in C, invalid in C++ */
typedef int type;
struct type
{
type
struct type *
};

memb;
next;

/* int */
/* pointer la struct */

void foo(type t, int i)


{
int
type;
struct type s;
type = i + t + sizeof(type);
s.memb = type;
}
(13) C face distinctie intre o functie declarata cu o lista vida de parametri
si o functie declarata cu o lista de parametri ce contine doar void; prima
este o functie neprototipata ce primeste un numar nespecificat de parametri,
in timp ce a doua este o functie prototipata care nu primeste parametri.
C++ nu face distinctie intre cele doua declaratii si le considera pe
amandoua ca insemnand o functie ce nu primeste parametri.
Exemple:
/* cod C */
extern int foo();
extern int bar(void);
void baz()
{
foo(0);
foo(1, 2);
bar();
bar(1);

/* parametri nespecificati */
/* fara parametri */

/* valid in C, invalid in C++ */


/* Valid C, invalid C++ */
/* valid in C si C++ */
/* Eroare si in C si in C++ */

}
/* cod C++ */
extern int xyz();
extern int xyz(void); /* la fel ca xyz() in C++,
diferit si invalid in C */
Nota: gcc versiunea 4.5.1 20101208 accepta prezenta ambelor declaratii ale
functiei xyz intr-un program C, dar la intalnirea unui apel va lua in

considerare declaratia cu void.


Daca vrem ca un fragment de cod sa fie compilabil atat in C cat si in C++,
cea mai buna solutie este ca functiile ce nu primesc parametrii sa fie
declarate intotdeauna cu un prototip ce foloseste explicit void:
/* se compileaza atat in C cat si in C++ */
int bosho(void)
{
...
}
Prototipurile de functii cu liste vide de parametri sunt considerate
invechite (deprecated) in C99.
TODO: exemplu de utilitate a functiilor cu lista vida de parametri in C.
(14) C++ este mai strict decat C in privinta atribuirilor de pointeri care
inlatura (discard) un calificator const (ex: atribuirea unei valori de tip
const int* unei variabile de tip int* ): in C++ aceasta atribuire este
invalida si genereaza un error (exceptand cazul cand se foloseste un o
conversie de tip (cast) explicita), in timp ce in C ea este permisa (desi
unele compilatoare emit un warning).
Exemplu:
int *p;
const int *q;
p = q;
/* valid in C, invalid in C++ */
p = (int *)q; /* valid in C si C++ */
Sfat: pentru a mari sansele ca un cod C sa fie valid si cu acelsai sens in
C++, la scrierea sa nu va mai bazati pe conventiile implicite (de exemplu
cast-uri implicite) ci explicitati toate operatiile dorite.
II. Constructii valide in C si C++, dar cu sensuri diferite
===========================================================
(1) Literalii caracter, cum ar fi 'a', sunt de tip int in C si char in C++;
in consecinta:
- sizeof 'a' va produce rezultate in general diferite in cele doua limbaje
(anume 1 in C++ si valoarea lui sizeof(int) in C);
- in C 'a' va fi intotdeauna o expresie cu semn, indiferent daca char este
sau nu un tip cu semn, in timp ce in C++ aceasta depinde de compilator.
(2) In C orice variabila globala (i.e. cu file scope) declarata cu
calificatorul const este vizibila si din alte fisiere sursa (are external
linkage), cu exceptia cazului cand este declarata si cu static.
In C++ orice variabila globala (i.e. cu file scope) declarata cu
calificatorul const nu este vizibila in afara fisierului sursa in care a
fost declarata (are internal linkage), cu exceptia cazului cand este
declarata si cu extern.
Exemple:
/* cod C */

const int i = 1;
/* external linkage */
extern const int j = 2; /* 'extern' este optional */
static const int k = 3; /* 'static' este obligatoriu */
/* cod C++ */
const int i = 1;
extern const int
static const int

/* internal linkage */
j = 2; /* 'extern' este obligatoriu */
k = 3; /* 'static' este optional */

De aceea practica recomandata este sa definim constantele cu un specificator


explicit static sau extern.
(3) Functiile inline (exista atat in C99 cat si in C++) au implicit
internal linkage in C si external linkage in C++.
(4) Unele diferente prezentate in sectiunea I pot fi exploatate si pentru a
crea cod care se compileaza in ambele limbaje dar se comporta diferit;
de exemplu, urmatoarea functie va returna valori diferite in C si C++:
extern int T;
int size(void)
{
struct T { int i; int j; };
return sizeof(T);
/*
C: return sizeof(int)
C++: return sizeof(struct T)
*/
}
Acest lucru se intampla deoarece in C trebuie sa punem cuvantul struct in
fata numelui tipurilor structura (astfel ca sizeof(T) se refera la variabila
T), in timp ce in C++ putem omite cuvantul struct (astfel ca sizeof(T) se
refera la numele T definit cu typedef-ul implicit)
Atentie ca efectul este diferit daca punem declaratia cu extern in
interiorul functiei: atunci, prezenta unui identificator cu acelasi nume
in domeniul (scope) functiei inhiba ca typedef-ul implicit sa aiba efect
in C++, iar functia va produce acelasi rezultat in C si C++ (anume valoarea
lui sizeof(int)).
Observam de asemenea ca ambiguitatea din exemplul de mai sus este cauzata
de utilizarea parantezelor impreuna cu operatorul sizeof; daca am fi
scris sizeof T (deci fara paranteze) s-ar fi considerat ca T este o
expresie si nu un tip, astfel incat fragmentul de cod de mai sus nu s-ar
fi compilat in C++.
(5) Atat C99 cat si C++ au un tip boolean, cu constantele true si false,
dar ele se comporta diferit.
In C++ tipul boolean este predefinit (built-in) si desemnat prin cuvantul
cheie bool.
In C99 tipul boolean este desemnat prin cuvantul cheie _Bool; in multe
privinte, el se comporta ca un unsigned int, dar conversiile dinspre alte

tipuri intregi sau pointeri inotdeauna sunt fortate la 0 si 1 (conversia


este 0 d.d. expresia in cauza se evalueaza la 0 si este 1 in celelalte
cazuri); fisierul header stdbool.h furnizeaza macro-urile bool, true si
false care sunt definite ca _Bool, 1 si respectiv 0.
III. Diferente de stil de programare
====================================
(1) C foloseste abordarea descendenta (top-down), in timp ce C++ foloseste
abordarea ascendenta (bottom-up):
in cazul C programul este formulat pas cu pas, fiecare pas este rafinat in
detalii, in timp ce in C++ sunt formulate intai elementele de baza, care
sunt apoi legate impreuna pentru a obtine sisteme mai largi.
(2) C este orientat pe functii (function-driven), in timp ce C++ este
orientat pe obiecte (object-driven):
blocurile din care este construit un program C sunt functiile, in timp ce
blocurile din care este construit un program C++ sunt obiectele.
TODO: exemplu de dezvoltare a unei aplicatii C si C++ (eventual ca un curs
separat).
Lectia 4: Clase si obiecte, incapsulare
=======================================
I. Clase si obiecte, 'this', structuri, uniuni
==============================================
Clasele si obiectele sunt conceptele de baza ale OOP.
Un OBIECT este o entitate ce poate incorpora date si/sau cod si care se
poate manevra ca un un tot logic, ca si cand ar fi o data obisnuita (putem
avea variabile cu valori obiecte). Tipul unui obiect s.n. CLASA.
Deci clasa este tipul, obiectul este data (INSTANTA a clasei).
Entitatile (date, cod) incorporate in clase/obiecte s.n. MEMBRII
clasei/obiectului; membrii-data s.n. ATRIBUTE, membri-functie s.n. METODE.
Capacitatea de a grupa mai multe entitati intr-un tot logic o aveau si
structurile sau uniunile din limbajul C, doar ca ele puteau grupa doar date.
Structurile si uniunile sunt prezente si in C++, iar datorita asemanarii
cu obiectele, ele sunt tratate unitar: clasele propriuzise, tipurile
structura, tipurile uniune sunt considerate toate clase, iar instantele lor
obiecte. Tipurile structura si uniune sunt considerate 'clase speciale'.
Astfel, toate aceste tipuri se declara si se folosesc asemanator, iar multe
proprietati sunt comune; exista si particularitati ale claselor speciale, pe
care le vom mentiona la momentul potrivit.
I.1. Clase si obiecte, this
===========================
Intr-o forma simplificata, o clasa se declara astfel:
class <Nume clasa>{

<Membri data si functie>


<Specificator de acces>:
<Membri data si functie>
<Specificator de acces>:
<Membri data si functie>
...
<Specificator de acces>:
<Membri data si functie>
} <Obiecte>;
In aceasta declaratie:
- 'class' este un cuvant cheie;
- <Specificator de acces> poate fi unul dintre cuvintele cheie 'private',
'protected', 'public';
- <Membri data si functie> este o lista de declaratii de membri data
si/sau membri functie;
- <Obiecte> este o lista optionala de obiecte declarate de clasa respectiva.
Membrii data (atributele) se declara asemanator ca in cazul structurilor
din C.
Membrii functie (metodele) pot fi definite complet (antet + corp) in cadrul
clasei, sau pot fi doar declarate in clasa (prototip) si definite ulterior,
sub forma:
<Tip returnat> <Nume clasa>::<Nume metoda> (<Parametri formali>) {
<Corp>
}
('::' este operatorul de REZOLUTIE (scope resolution operator)).
Cand sunt definite complet in cadrul clasei, metodele sunt implicit
considerate ca fiind declarate 'inline'. Altfel, sunt implicit functii
obisnuite, dar putem cere pentru ele implementarea inline in mod explicit,
scriind:
inline <Tip returnat> <Nume clasa>::<Nume metoda> (<Parametri formali>) {
<Corp>
}
Pentru detalii despre funtiile/metodele inline, a se vedea lectia ***.
Atributele si metodele pot avea calificatori auxiliari, ca 'static',
'const', etc.
Atributele statice, pe langa declaratia din cadrul clasei, trebuie sa aiba
si o definitie exterioara acesteia (caz in care vom folosi <Nume clasa>::),
de exemplu:
class <Nume clasa>{
...
static <Tip> <Nume>;
...
} <Obiecte>;
...
<Tip> <Nume clasa>::<Nume>;
Intr-adevar, declaratia clasei contine doar declaratiile atributelor sale
(codul care le descrie), nu si definitia acestora (codul care le face sa

existe, in particular le aloca memorie); un atribut nestatic are instante


disjuncte pentru fiecare obiect al clasei, carora li se aloca memorie la
crearea obiectelor; un atribut static are o instanta unica pentru toate
obiectele clasei si trebuie creat (alocat) inaintea acestor obiecte - de
aceea este necesara definitia exterioara.
De asemenea, atributele statice pot avea initializari, scrise in cadrul
clasei (doar daca sunt si 'const') sau in definitia exterioara. Atributele
nestatice nu pot avea initializari. Atributele 'const' trebuie sa fie
initializate (cele statice in interiorul sau exteriorul declaratiei clasei,
cele nestatice in listele de initializare ale constructorilor); exemple:
#include<iostream>
using namespace std;
class cls{
int a = 0;
// eroare, atribut nestatic initializat
static int b = 1;// eroare, atribut static non-const initalizat in clasa
static int c;
static const int d = 3;
static const int e;
const int f;
int g; // atribut non-static, non-const; se poate lasa neinitializat
// si primeste o valoare implicita
public:
cls():f(5){g = 6;}
void print(){
cout << c << " " << d << " " << e << " " << f << " " << g << endl;
}
} x;
int cls::c = 2;
const int cls::e = 4;
int main(){
cls x;
x.print(); // afisaza: 2 3 4 5 6
}
Obs: in loc de 'cls():f(5){g = 6;}' puteam scrie 'cls():f(5),g(6){}',
dar nu 'cls() {f = 5; g = 6;}', deoarece membrul constant 'f' poate
fi initializat doar in lista de initializare a constructorului 'cls'
(unde se aplica un constructor pentru 'int') si nu in corpul lui 'cls'
(unde se aplica operatorul de atribuire pentru 'int').
In cazul atributelor care sunt la randul lor obiecte, definirea si
initializarea presupune apelarea constructorilor acestora.
Pentru alte detalii, a se vedea capitolul IV din aceasta lectie.
Lista optionala <Obiecte> declara niste instante ale clasei, asemanator
ca in cazul structurilor. Ulterior, putem declara si alte instante, scriind:
<Nume clasa> <Obiecte>;
observam ca nu mai este necesar cuvantul 'class' inaintea numelui clasei, dar
nici interzis - putem scrie:
class <Nume clasa> <Obiecte>;
Specificatorii de acces precizeaza ACCESIBILITATEA membrilor declarati

dupa el; ca accesibilitate, membrii (atribute sau metode) pot fi: privati
(private), protejati (protected), publici (public). Membrii privati pot fi
invocati doar in metodele clasei si in functiile prietene (friend) acesteia,
cei publici pot fi invocati din orice parte a programului; in lipsa unei
specificari de acces, membrii clasei sunt implicit privati (atentie, ca la
un tip structura sunt implicit publici). Pentru alte detalii, a se vedea
capitolul II din aceasta lectie.
Pentru o instanta a clasei, membrii se pot accesa folosind sintaxa cu '.',
ca in cazul structurilor:
<Nume obiect>.<Nume membru>
Exemplu:
========
#include<iostream>
using namespace std;
class complex{
double re, im; // implicit privati
public:
void setre(double x) {re = x;} // metoda
void setim(double);
// metoda
double getre();
// metoda
double getim();
// metoda
} a, b[3];

publica, implicit functie inline


publica
publica
publica

inline void complex::setim(double x) {im = x;} // explicit functie inline


double complex::getre() {return re;} // implicit functie obisnuita
double complex::getim() {return im;} // implicit functie obisnuita
int main(){
complex c;
a.re = 3.14; // eroare: 'r' este membru privat al clasei 'complex'
// iar 'main' nu este metoda sau friend a clasei 'complex'
a.setre(3.14); // corect, 'setre' si 'setim' sunt metode publice, se
a.setim(10); // pot invoca de oriunde; 'a' va contine numarul
// complex 3.14 + i * 10
cout << a.getre() << " + i*" << a.getim() << endl;
// corect, 'getre' si 'getim' sunt metode publice, se pot invoca de
// oriunde; se afisaza: 3.14 + i * 10
c.setre(1); c.setim(2);
for(int i=0; i<3; ++i) b[i] = c;
// corect, pentru orice clasa definita de utilizator compilatorul
// adauga implicit o supraincarcare a operatorului '=', care copiaza
// bit cu bit locatia sursa in cea destinatie; daca aceasta functionare
// nu ne convine, putem inlocui acest '=' cu o alta supraincarcare,
// definita explicit
if(a == c) cout<<"OK\n";
// eroare, compilatorul nu adauga implicit si o supraincarcare a
// operatorului '==' (daca dorim o asemenea supraincarcare, trebuie
// sa o adaugam explicit)
}
Comentarii:
- in program avem cinci obiecte de clasa 'complex': a, b[0], b[1], b[2]

(globale) si c (locala lui 'main'); 'a' si vectorul 'b' au fost declarate


odata cu clasa, 'c' ulterior (vedem ca nu a mai fost nevoie sa adaugam
cuvantul 'class');
- fiecare dintre cele cinci obiecte are proprii sai membri 're', 'im',
'setre', 'setim', 'getre', 'getim' iar daca vrem sa ne referim la un anumit
membru al unui anumit obiect, trebuie sa calificam numele membrului cu
numele obiectului si '.'; asadar, 'a.re' este o alta entitate decat 'c.re';
daca intr-o metoda nestatica a unui obiect invocam un membru al aceleiasi
clase necalificat cu vreun obiect, se considera ca este vorba de membrul
obiectului care a apelat metoda; asadar, instructiunea 're = x' din corpul
metodei 'setre' a lui 'a' executa de fapt 'a.re = x' iar cea din corpul
aceleiasi metode a lui 'c' executa de fapt 'c.re = x'; implementarea
acestui comportament se bazeaza pe folosirea parametrului ascuns 'this',
a se vedea mai jos;
- atributele 're' si 'im' sunt implicit private (deoarece nu exista nici un
specificator de acces deasupra lor) iar metodele 'setre', 'setim', 'getre',
'getim' sunt publice (deoarece cel mai apropiat (si singurul) specificator
de acces aflat deasupra lor este 'public');
de aceea, membrul privat 're' a putut fi invocat in functiile 'setre',
'getre', deoarece sunt metode ale clasei, si nu a putut fi invocat in
functia 'main', deoarece aceasta nu este metoda a clasei si nici prietena
a acesteia (conteaza doar invocarea numelui 're', nu are relevanta prezenta
vreunei calificari cu un obiect si '.');
- observam ca obiectele se pot manevra ca un tot unitar, prin atribuire cu
'=', comparare cu '==', etc.; este insa necesar sa existe supraincarcari
ale acestor operatori cu parametri de tip 'complex'; pentru '=',
compilatorul adauga o supraincarcare implicit (dar o putem inlocui cu o
alta), pentru '==' nu (trebuie sa o adaugam explicit); pentru alte detalii
a se vedea lectia V.
Asa cum am spus mai sus, obiectele unei clase au locatii disjuncte pentru
atributele nestatice si locatii comune pentru cele statice; exemplu:
class c{
public:
int x;
static int y;
} a,b;
int c::y;
a.x = 1; b.x = 2; cout << a.x << " " << b.x << endl; // afisaza: 1 2
a.y = 3; b.y = 4; cout << a.y << " " << b.y << endl; // afisaza: 4 4
intr-adevar, 'a.x' si 'b.x' au locatii disjuncte, 'a.y' si 'b.y' au
aceeasi locatie.
Metodele unei clase nu au mai multe copii, cate una pentru fiecare obiect,
ci fiecare exista in cate un singur exemplar program, iar toate invocarile ei
de catre diverse obiecte sunt traduse de compilator prin grupuri de
instructiuni care contin un 'call' (instructiunea de apel de procedura in
limbaj masina) catre o aceeasi adresa (a corpului metodei).
Astfel, in programul anterior cu numere complexe exista un singur exemplar
al functiei 'setre', iar atat apelul 'a.setre(3.14)' cat si apelul
'c.setre(1)' produc salt catre o aceesi adresa (a corpului lui 'setre',
determinata de compilator si scrisa ca atare, ca o constanta, in programul
executabil - early binding). Se pune problema cum poate un acelasi bloc de
instructiuni (corpul lui 'setre') sa efectueze operatii diferite in functie
de obiectul care a produs saltul la el ('a.re = x', in cazul apelului
'a.setre(3.14)', si 'c.re = x', in cazul apelului c.setre(1)). Efectul se

bazeaza pe folosirea unui parametru ascuns (adaugat automat de compilator),


prin care se transmite adresa obiectului apelant.
Mai exact:
- orice metoda nestatica primeste implicit un parametru formal suplimentar,
care s.n. 'this' si este de tip pointer constant la clasa din care face parte
metoda (daca clasa este 'C', tipul lui 'this' este 'C * const');
- orice invocare a unei metode nestatice calificata cu un obiect primeste
implicit un parametru actual suplimentar, care corespunde lui 'this' si
care este adresa obiectului apelant (daca obiectul este 'x', parametrul
actual este '&x'); daca apelam metoda fara calificare (asa ceva se poate din
interiorul unei metode nestatice a aceleiasi clase), ea va primi valoarea lui
'this' din metoda apelanta;
- in corpul unei metode nestatice, invocarea membrilor fara calificare este
calificata implicit cu 'this->'.
Astfel, in corpul metodelor nestatice compilatorul traduce invocarile
membrilor necalificati prin expresii generale care selecteaza obiectul
posesor la momentul rularii, in functie de datele curente (anume valoarea
lui 'this', care este late-bound).
Notam ca 'this', chiar daca nu a fost declarata explicit, exista in program
si poate fi invocat explicit in instructiuni scrise de utilizator (de exemplu
pentru a ne referi la obiectul curent).
Exemplu: urmatorul exemplu este preluat (cu mici modificari) de pe site-ul:
======== http://publib.boulder.ibm.com/infocenter/lnxpcomp/v7v91/index.jsp
si arata o implementare a conceptului de string ca o clasa 'X' cu metode
pentru setare/furnizare sir, furnizare lungime sir, concatenarea unui sir
la cel curent, copierea unui obiect string in cel curent, afisare; codul
este dat in doua variante echivalente, una care foloseste parametrul ascuns
'this' si una care foloseste un parametru explicit 'THIS' pentru a simula
folosirea implicita a lui 'this':
Varianta care foloseste implicit 'this':
#include<iostream>
#include<string.h>
using namespace std;
class X {
int len;
char *ptr;
public:
int GetLen() {return len;}
char * GetPtr() {return ptr;}
X& Set(char *);
X& Cat(char *);
X& Copy(X&);
void Print();
};
X& X::Set(char *pc) {
len = strlen(pc); ptr = new char[len]; strcpy(ptr, pc); return *this;
}
X& X::Cat(char *pc) {
len += strlen(pc); strcat(ptr,pc); return *this;
}

X& X::Copy(X& x) {
Set(x.GetPtr()); return *this;
}
void X::Print() {
cout << ptr << endl;
}
int main() {
X xobj1;
xobj1.Set("abcd").Cat("efgh").Print();
X xobj2;
xobj2.Copy(xobj1).Cat("ijkl").Print();
}

// afisaza: abcdefgh
// afisaza: abcdefghijkl

Varianta care simuleaza folosirea lui 'this' cu ajutorul parametrului


explicit 'THIS':
#include<iostream>
#include<string.h>
using namespace std;
class X {
int len;
char *ptr;
public:
int GetLen (X* const THIS) {return THIS->len;}
char * GetPtr (X* const THIS) {return THIS->ptr;}
X& Set(X* const, char *);
X& Cat(X* const, char *);
X& Copy(X* const, X&);
void Print(X* const);
};
X& X::Set(X* const THIS, char *pc) {
THIS->len = strlen(pc); THIS->ptr = new char[THIS->len];
strcpy(THIS->ptr, pc); return *THIS;
}
X& X::Cat(X* const THIS, char *pc) {
THIS->len += strlen(pc); strcat(THIS->ptr, pc); return *THIS;
}
X& X::Copy(X* const THIS, X& x) {
THIS->Set(THIS, x.GetPtr(&x)); return *THIS;
}
void X::Print(X* const THIS) {
cout << THIS->ptr << endl;
}
int main() {
X xobj1;
xobj1.Set(&xobj1 , "abcd").Cat(&xobj1 , "efgh").Print(&xobj1);
// afsaza: abcdefgh
X xobj2;
xobj2.Copy(&xobj2 , xobj1).Cat(&xobj2 , "ijkl").Print(&xobj2);

// afsaza: abcdefghijkl
}
Comentarii:
- in ambele variante exista parametrul 'this', deoarece este adaugat implicit
de compilator; prima varianta se bazeaza insa pe acesta, deoarece invoca
membrii clasei fara calificare si indica obiectul curent prin '*this';
a doua varianta nu foloseste 'this' (desi il are) si acceseaza informatiile
necesare cu 'THIS' manevrat explicit;
- observam ca 'this' nu trebuie declarat explicit (il adauga compilatorul
implicit), dar il putem utiliza in instructiuni explicit, de exemplu:
'return *this;'
- faptul ca metodele 'Set', 'Cat', 'Copy' returneaza obiectul curent prin
referinta ne permite sa facem calificari inlantuite, de exemplu:
'xobj1.Set("abcd").Cat("efgh").Print();'
care este echivalenta cu:
'((xobj1.Set("abcd")).Cat("efgh")).Print();'
- evident, nu exista confuzie intre cuvintele 'this' si 'THIS', deoarece
C++, ca si C, este case sensitive !
Alte caracteristicile ale lui 'this':
- nu poate fi declarat explicit (este un cuvant cheie), nu i se poate
asigna o valoare explicit (este pointer constant);
- metodele statice si functiile independente nu au parametrul 'this';
pentru detalii a se vedea capitolul III din aceasta lectie;
- metodele nestatice ale unei clase 'C' declarate cu 'const', respectiv
'volatile', au 'this' de tipul 'const C * const', respectiv 'volatile
C * const'; de exemplu:
class C {
int a;
int f() const { return a++; }
};
genereaza o eroare de compilare, deoarece 'a++' este compilat prin
'this->a++', care este echivalent cu '(this->a)++' iar 'this' are
declaratia (implicita) 'const C * const this', deci memoria nu poate fi
accesata prin 'this' pentru modificare (ci doar pentru consultare);
practic, variabila 'this->a' este considerata zona constanta;
pentru alte detalii legate de metodele declarate cu 'const' sau
'volatile' a se vedea capitolele V si VI din aceasta lectie;
- daca o variabila locala sau un parametru al unei metode nestatice are
acelasi nume ca un membru al clasei, invocarea numelui respectiv in
interiorul metodei fara calificare va desemna variabila, respectiv
parametrul; daca vrem sa desemnam membrul, calificam explicit cu 'this->';
exemplu:
#include <iostream>
using namespace std;
class X {
int a;
public:
void Set_a(int a) {
// Pointerul 'this' este folosit pentru regasirea lui 'xobj.a'
// mascat de parametrul 'a'

this->a = a;
}
void Print_a() { cout << "a = " << a << endl; }
};
int main() {
X xobj; int a = 5;
xobj.Set_a(a); xobj.Print_a(); // afisaza: a = 5
}
Daca numele unui membru al clasei nu este mascat, invocarea numelui
respectiv este echivalenta cu invocarea numelui respectiv calificat cu
'this->'.
Putem invoca membrii unei clase cu specificarea domeniului (scope) acestora,
'clasa::'. Acest lucru este util atunci cand numele unor membri este mascat de
alte nume (de exemplu pentru a distinge intre un membru mostenit si unul nou
adaugat, cu acelasi nume) si pentru invocarea membrilor statici fara
precizarea obiectului in functii ce nu sunt metode ale clasei (membrii
statici au locatii comune pentru toate obiectele clasei si pot fi invocati
fara a preciza un obiect, dar cand facem acest lucru in afara domeniului
clasei trebuie precizat domeniul lor).
Exemplu:
========
#include<iostream>
using namespace std;
class baz{
public:
int i, j, k, t;
};
class der: public baz{
public:
int i, j, a, b;
void f();
};
// 'der' are doi 'i' si doi 'j' (cate unul mostenit si unul nou);
// mai are 'k', 't' (mosteniti), 'a', 'b', 'f'
void der::f(){
int i, k, a; // variabile locala; sunt diferite de membrii 'i', 'k' 'a'
i = 1;
// 'i' local
der::i = 2;
// membrul 'i' nou
baz::i = 3; der::baz::i = 30; // membrul 'i' mostenit
j = 4; der::j = 40;
// membrul 'j' nou
baz::j = 5; der::baz::j = 50; // membrul 'j' mostenit
k = 6;
// 'k' local
der::k = 7; baz::k = 70; der::baz::k = 700; // membrul 'k'
a = 8;
// 'a' local
der::a = 9; // membrul 'a'
t = 11; der::t = 110; baz::t = 1100; der::baz::t = 11000; // membrul 't'
b = 22; der::b = 220;
// membrul 'b'

cout << i << "


<< j << "
<< k << "
<< a << "
<< t << "
<< b << "
// afisaza:
// 1 2 30 30 40

"
"
"
"
"
"

<<
<<
<<
<<
<<
<<

der::i
der::j
der::k
der::a
der::t
der::b

<<
<<
<<
<<
<<
<<

" " <<


" " <<
" " <<
" "
" " <<
endl;

baz::i << " " << der::baz::i << " "


baz::j << " " << der::baz::j << " "
baz::k << " " << der::baz::k << " "
baz::t << " " << der::baz::t << " "

40 50 50 6 700 700 700 8 9 11000 11000 11000 11000 220 220

}
int main(){
der x; x.f();
}
Comentarii:
- Variabila locala 'i' mascheaza membrul nou 'i', care mascheaza membrul
mostenit 'i'; prin precizarea domeniului 'clasa::' le putem accesa.
- Celelalte exemple arata ca precizarea domeniului 'clasa::' in cazul unei
entitati vizibile implicit in acel domeniu este inutila (ex. 'j' este
echivalent cu 'der::j'.
Exemplu:
========
#include<iostream>
using namespace std;
class baz{
public:
int i;
int f(int);
int f(int, int);
int g(); int k;
void h(int);
};
int baz::f(int x){cout << "int baz::f(int)\n"; return 0;}
int baz::f(int x, int y){cout << "int baz::f(int, int)\n"; return 0;}
int baz::g(){cout << "int baz::g()\n"; return 0;}
void baz::h(int x){cout << "int baz::h(int)\n";}
class der: public baz{
public:
double i;
double f(int);
void f(int, int);
int g; int k();
void h(int, int);
void afis();
};
double der::f(int x){cout << "double der::f(int)\n"; return 12.3;}
void der::f(int x, int y){cout << "void der::f(int, int)\n";}
int der::k(){cout << "int der::k()\n"; return 0;}
void der::h(int x, int y){cout << "void der::h(int, int)\n";}
void der::afis(){
baz::i = 1; i = 2;
cout << baz::i << " " << der::i << " " << endl;
// afisaza: 1 2
// deci atributul 'i' din clasa 'der' a mascat atributul 'i' din clasa

//
//

'baz', chiar daca au tipuri diferite si am folosit valori de tipuri


corespunzatoare (cu 'i = 2' s-a atribut tot 'baz::i');

int a, b;
a = f(3); b = baz::f(4);
a = f(5, 6); // eroare, nu se poate atribui 'void' unei variabile 'int'
a = baz::f(5, 6);
// afisaza:
// double der::f(int)
// int baz::f(int)
// int baz::f(int, int)
// deci metodele 'f' din clasa 'der' mascheaza metodele 'f' din clasa
// 'baz' chiar daca difera tipul returnat iar 'a' si 'b' sunt de tipul
// 'int' (tipul returnat de metodele din 'baz');
h(6); // eroare, nu este gasita o metoda cu signatura potrivita in 'der'
baz::h(7);
h(8, 9);
// afisaza:
// int baz::h(int)
// void der::h(int, int)
// deci metoda 'h' din clasa 'der' o mascheaza pe cea din clasa 'baz'
// chiar daca difera signatura si am apelat cu numar adecvat de parametri
// actuali
a = g(); // eroare, 'g' nu este functie
a = baz::g();
k = 11; // eroare, membrul stang nu este l-value intreaga
baz::k = 10;
g = k(); // OK, sunt 'g' si 'k' din clasa 'der'
cout << a << " " << baz::k << " " << g << endl;
// afisaza:
// int baz::g()
// int der::k()
// 0 10 0
// deci numele din clasa 'der' le mascheaza pe cele din clasa 'baz', chiar
// daca un nume desemneaza intr-o clasa un atribut si in cealalta o
// metoda
}
int main(){
der ob;
ob.afis();
}
Comentariu: numele declarate in clasa derivata le pot masca pe cele declarate
in clasa de baza chiar daca desemneaza entitati de natura diferita
(atribute/metode) si tip/signatura diferita; in toate cazurile, invocarea
cu specificarea domeniului 'baz::' ne ajuta sa le accesam si pe cele mascate.
Exemplu:
========
class cls{
public:
static int i;
void f();
};
int cls::i;
// 'i' are locatie comuna pentru toate obiectele lui 'cls';

// el se poate invoca si fara a preciza obiectul, dar cand facem aceasta


// in afara clasei trebuie precizat domeniul 'cls::'
void cls::f(){
i = 1; cls::i = 2;
}
// in 'f' suntem in domeniul clasei, deci precizarea domeniului
// 'cls::' este inutila; 'i' este echivalent cu 'cls::i'
int main(){
cls x;
x.i = 3; x.cls::i = 4; cls::i = 5;
i = 6; // eroare, numele 'i' nu este necunoscut aici
// in'main' nu suntem in domeniul clasei ('main' nu este metoda a ei),
// asa ca desi putem invoca 'i' fara precizarea obiectului,
// cand omitem obiectul trebuie sa precizam clasa;
// deci, putem scrie 'cls::i' dar nu doar 'i';
// 'x.i', 'x.cls::i', 'cls::i' sunt echivalente
x.f(); x.cls::f();
f();
// eroare, numele 'f' nu este necunoscut aici
cls::f(); // eroare, trebuie precizat un obiect
// 'f' este nestatica, deci depinde de obiect, iar 'main' nu are un
// obiect curent; 'x.f()' si 'x.cls::f()' sunt echivalente
}
In programul de mai sus 'i' si 'cls::i' din 'f', 'x.i', x.cls::i' si
'cls::i' din 'main' desemneaza toate cinci aceeasi locatie.
O DECLARATIE DE CLASA INCOMPLETA (INCOMPLETE CLASS DECLARATION) este o
declaratie de clasa ce nu specifica nici un membru (si nici acoladele).
Pana cand declaratia clasei nu va fi scrisa ulterior complet, nu se pot
declara obiecte ale clasei si nici nu se pot invoca membrii sai.
O declaratie incompleta ne permite insa sa ne referim la clasa inainte de a
o declara complet, cu conditia ca dimensiunea (sizeof) clasei sa nu trebuiasca
a fi cunoscuta; de exemplu putem declara referinte sau pointeri la ea.
Exemplu:
========
class first;

// declaratie incompleta a clasei 'first'

class second{
// declaratie completa a clasei 'second'
first* oneptr;
// pointer la clasa 'first'; se refera la clasa 'first' inaintea
// declaratiei sale complete (implementarea lui 'oneptr' nu necesita
// cunoasterea lui 'sizeof(first)' deoarece toti pointerii au aceeasi
// dimensiune
first &oneref;
// similar
first one;
// eroare, nu putem declara un obiect al unei clase declarate incomplet
int x, y;
public:
second();
};
class first{
// declaratia completa a clasei 'first'
second two; // se defineste un obiect al clasei 'second'

int z;
};
first ob;
second::second():oneref(ob){}
Comentariu: Definitia lui 'ob' si cea a lui 'second' (anume liniile
'first ob;' si 'second::second():oneref(ob){}') trebuie puse dupa
declaratia completa a lui 'first', altfel se semnaleaza aroare.
Daca declaram o clasa cu lista vida de membri (deci sunt prezente acoladele),
aceasta este o declaratie de clasa completa.
Exemplu:
========
class X;
class Z {};
class Y{
public:
X yobj;
Z zobj;
};

// declaratie de clasa incompleta


// lista de membri vida (dar declaratie de clasa completa)
// eroare, nu se poate crea un obiect al unei clase incomplete
// valid

Alte restrictii legate de clase:


- nici un atribut nu poate fi obiect al clasei care se declara (dar
poate fi pointer la ea):
class c{
c x;
c *y;
};

// eroare
// corect

intr-adevar, daca ar exista atribute de aceeasi clasa, compilatorul nu ar


putea calcula dimensiunea (sizeof) instantelor clasei (calculul ar intra
intr-o recursie infinita); pointerii insa au toti aceeasi dimensiune
(sizeof), deci compilatorul 'stie' ce dimensiune are 'x', chiar daca (inca)
nu 'stie' ce inseamna 'c';
- nici un membru nu poate fi declarat ca 'auto', 'extern' sau 'register':
class c{
auto int x;
extern int y;
register int z;
};

// eroare
// eroare
// eroare

I.2 Structuri
=============
In C++, tipurile structura sunt foarte asemanatoare cu clasele; in particular
ele pot avea atribute si metode, constructori si destructori, specificatori de
acces, metodele nestatice au 'this', etc.; ele pot mosteni alte tipuri
structura sau clase; de asemenea, clasele pot mosteni tipuri structura sau
clase.
De aceea, sintaxa folosita in legatura cu tipurile structura este

asemanatoare cu cea folosita in cazul claselor, dar in locul cuvantului


'class' se foloseste cuvantul 'struct' (similar, el este necesar doar la
declararea tipului structra, nu si la utilizarea sa, unde este optional).
Singurele diferente intre clase si tipuri structura sunt:
- in absenta unei specificari de acces, membrii unei clase sunt implicit
privati, iar membrii unei structuri sunt implicit publici; de exemplu,
urmatoarele declaratii descriu tipuri similare:
class c{
int a;
public:
void set(int x){a = x;}
int get(){return a;}
};
struct s{
void set(int x){a = x;}
int get(){return a;}
private:
int a;
};

// implicit privat
// explicit public
// explicit public

// implicit public
// implicit public
// explicit privat

motivul acestei diferente este compatibilitatea cu limbajul C; de exemplu,


daca membrii unei structuri ar fi implicit privati (ca la clase) urmatorul
cod, care este valid in C:
#include<string.h>
struct persoana{
char nume[10];
int varsta;
};
int main(){
struct persoana x;
strcpy(x.nume, "Ion");
x.varsta = 10;
return 0;
}
devine invalid in C++, deoarece aici membrii capata o accesibilitate
(ceea ce nu exista in C) si fiind implicit privati (in C oricum nu puteam
specifica 'public:' deoarece nu exista accesibilitate) nu ar putea fi
accesati in 'main' (care nu e metoda si nici prietena a tipului structura,
conceptele care de asemenea neexistand in C).
- clasele mostenesc implicit privat, tipurile structura mostenesc implicit
public (pe alte clase sau tipuri structura) - detalii in lectia ***;
exemplu:
class c{public: int a;};
struct s{int b;};
class cls:c,s {}; // 'cls' mosteneste implicit privat pe 'c' si 's'
// deci in 'cls' membrii 'a' si 'b' sunt privati
struct str:c,s {}; // 'str' mosteneste imlicit public pe 'c' si 's'

// deci in 'str' membrii 'a' si 'b' raman publici


// (ca in 'c' si 's')
int main(){
cls x; str y;
x.a = 1; // eroare
x.b = 1; // eroare
y.a = 1; // OK
y.b = 1; // OK
}
Reamintim ca tipurile structura sunt considerate tot clase, 'speciale'.
Ele au fost preluate din C si li s-au extins capabilitatile a.i. sa fie
similare claselor. Faptul ca in C++ exista si clase si tipuri structura, desi
ambele au aproximativ aceeasi putere are urmatoarele justificari:
- tipurile structura trebuie pastrate, pentru a avea compatibilitate cu C;
nu exista nici un motiv pentru ca lor sa nu li se extinda capabilitatile,
dar extinderea trebuie facuta a.i. sa nu se intre in conflict cu
proprietatile lor din C - deci evolutia lor in viitor este supusa unor
constrangeri;
- clasele nu trebuie neaparat sa aiba proprietati specifice C, asa ca
ele pot evolua in continuare liber.
Ca stil de programare, recomandam:
- folosirea claselor, atunci cand vrem sa modelam o entitate cu
'personalitate', care sa aiba o reactie de opozitie la anumite interventii
din afara - de ex. o fiinta vie; aici dorim sa avem incapsulare si sa
ascundem o parte din functionalitatea entitatii ca privata, permitand
un acces la ea din afara limitat, prin intermediul unei interfete publice;
- folosirea tipurilor structura, atunci cand vrem sa modelam o entitate
'nevie', care permite un acces total din afara la functionalitatea ei de ex. o trusa de scule.
I.3 Uniuni
==========
In limbajul C, structurile si uniunile sunt definite ca grupari de date
membre intr-un tot logic a.i.:
- structurile garanteaza ca orice membru asignat isi reda corect valoarea;
- uniunile garanteaza ca doar ultimul membru asignat isi reda corect valoarea
(deci asignand un membru se pot altera valorile altor membri).
Astfel, o structura poate stoca la un moment dat un n-uplu de valori de
niste tipuri date, in timp ce o uniune poate stoca la un moment dat doar o
singura valoare, de unul dintre tipurile date.
Daca privim un tip ca o multime de valori, structurile modeleaza in mod
natural produsele carteziene de multimi, in timp ce uniunile modeleaza in
mod natural reuniunile de multimi.
Pentru a avea comportamentul descris mai sus, de obicei structurile sunt
implementate a.i. membrii sa aiba locatii disjuncte, iar uniunile a.i.
membrii sa aiba locatii nedisjuncte (se economiseste astfel memorie).
Nu exista insa reguli stabilite in privinta ordinii de dispunere a membrilor
in memorie, alinierii lor la diverse adrese, alocarii lor adiacent sau spatiat
(specularea acestor aspecte in program poate conduce a un cod neportabil).
Ca si tipurile structura, si tipurile uniune sunt prezente in limbajul C++
ca niste clase speciale. Sintaxa folosita in legatura cu tipurile uniune este
si ea asemanatoare cu cea folosita in cazul claselor, dar in locul cuvantului
'class' se foloseste cuvantul 'union' (similar, el este necesar doar la

declararea tipului uniune, nu si la utilizarea sa, unde este optional).


Tipurile uniune au majoritatea caracteristicilor prezentate mai sus pentru
clase; in particular, ele pot avea atribute si metode, constructori si
destructori, specificatori de acces, metodele nestatice au 'this', etc.
Ca si in cazul tipurilor structura, membrii tipurilor uniune sunt implicit
publici.
De asemenea, tipurile uniune au toate capabilitatile din C, cea mai
importanta fiind aceea ca atributele lor impart aceeasi locatie de memorie.
Exemplu: Uniune cu ajutorul careia putem afisa octetii (unsigned char) din
======== reprezentarea interna a unor date de diverse tipuri: int, float,
double; octetii sunt parcursi de la cel mai putin semnificativ (conform
conventiei little endian el se afla la adresa cea mai mica) la cel mai
semnificativ, iar valorile lor se afisaza ca intregi zecimali; se presupune
ca uniunile sunt implementate a.i. membrii au locatii suprapuse, aliniate
la acelasi inceput, iar tipurile int, float, double au dimensiunile (sizeof)
4, 4, respectiv 8:
#include<iostream>
using namespace std;
union rep{
void repint(int);
void repfloat(float);
void repdouble(double);
private:
int x; float y; double z; unsigned char v[8];
};
void rep::repint(int n) {
x = n;
for(int i = 0; i < sizeof(int); ++i) cout << (int)v[i]<<" ";
cout << endl;
}
void rep::repfloat(float n) {
y = n;
for(int i = 0; i < sizeof(float); ++i) cout << (int)v[i]<<" ";
cout << endl;
}
void rep::repdouble(double n) {
z = n;
for(int i = 0; i < sizeof(double); ++i) cout << (int)v[i]<<" ";
cout << endl;
}
int main(){
rep r;
r.repint(10);
// afisaza: 10 0 0 0
r.repfloat(10.0); // afisaza: 0 0 32 65
r.repdouble(10.0); // afisaza: 0 0 0 0 0 0 36 64
}
Intr-adevar, 'x', 'y', 'z', 'v' au locatii suprapuse si aliniate la acelasi
inceput, a.i. daca de exemplu asignam 'x' (care are sizeof-ul 4) cu o
valoare, cei patru octeti din reprezentarea ei interna vor ocupa de fapt
locatiile lui 'v[0]', 'v[1]', 'v[2]', respectiv 'v[3]'.

Restrictii legate de uniuni:


- un tip uniune nu poate mosteni nici un alt tip de clasa si nu poate fi
mostenita, de nici un alt tip de clasa;
- un tip uniune nu poate avea metode virtuale;
- un tip uniune nu poate avea atribute 'static';
- un tip uniune nu poate avea atribute-obiect care au constructori sau
destructori expliciti (i.e. definiti de utilizator) sau au supraincarcat
operatorul '='.
I.4. Uniuni anonime
===================
In C++ exista un tip special de uniune, numit UNIUNE ANONIMA.
O uniune anonima nu contine un nume de tip si nu se pot declara variabile de
tipului de uniune respectiv. In schimb, ea spune compilatorului ca atributele
sale impart aceeasi locatie. Acestea se utilizeaza direct, fara sintaxa cu '.'
si au ca domeniu (scope) blocul in care a fost definita uniunea - in
particular, ele nu pot avea acelasi nume cu alti identificatori declarati in
blocul respectiv.
Practic, daca vrem ca mai multe variabile dintr-un bloc sa imparta aceeasi
locatie, le grupam intr-o uniune anonima (apoi le folosim ca pe niste
variabile locale obisnuite, declarate in blocul respectiv).
Exemplu:
========
#include<iostream>
#include<string.h>
using namespace std;
int main(){
// definirea uniunii anonime
union {
long l;
double d;
char s[4];
};
// acum, accesul la membrii uniunii este direct:
l = 1000000;
cout << l << endl; // afisaza: 1000000
d = 3.14;
cout << d << endl; // afisaza: 3.14
strcpy(s, "hi");
cout << s << endl; // afisaza: hi
}
Toate restrictiile legate de uniuni se aplica si uniunilor anonime, cu cateva
completari:
- uniunile anonime pot avea doar atribute, nu si metode;
- membrii uniunilor anonime nu pot fi 'private' sau 'protected';

- uniunile anonime globale trebuie declarate 'static':


static union{
...
};
astfel, ele nu vor fi vizibile in alte fisiere sursa.
I.5. Clase agregat (aggregate class)
====================================
O CLASA AGREGAT (AGGREGATE CLASS) este o clasa care:
- nu are constructori definiti de utilizator;
- nu are atribute non-statice care sunt 'private' sau 'protected';
- nu mosteneste alte clase (nu are clase de baza);
- nu are metode virtuale.
o clasa agregat poate fi initializata folosind o lista de clauze de
initializare separate prin virgula si inclusa intre acolade.
Exemplu:
========
struct C
{
int a;
double b;
};
struct D
{
int a;
double b;
C c;
};
// initializam un obiect 'c' de tip 'C' cu o lista de initializatori
C c = {1, 2.0};
// 'D' are un sub-agregat de tip 'C';
// in asemenea cazuri clauzele de initializare pot fi imbricate
D d = {10, 20.0, {1, 2.0}};
I.6. Structuri POD (POD-struct)
===============================
O STRUCTURA POD (POD-STRUCT = Plain Old Data Structure) este o clasa agregat
care:
- nu are atribute non-statice care sunt de tip non-POD-struct, non-POD-union
(sau masive de asemenea tipuri) sau referinte;
- nu are operator de atribuire definit de utilizator;

- nu are destructor definit de utilizator.


S-ar putea spune ca o structura POD este echivalentul C++ al unei structuri
C. In cele mai multe cazuri, o structura POD va avea acelasi format in
memorie (memory layout) ca o structura corespunzatoare declarata in C. Din
acest motiv, structurile POD sunt uneori numite informal 'structuri in stil C'
('C-style structs').
Proprietati prezente atat la structurile din C cat si la structurile POD
din C++:
- atributele sunt alocate a.i. membrii adaugati ulterior sa aiba adrese mai
mari in cadrul unui obiect, cu exceptia cazului cand sunt separati printr-un
specificator de acces;
- doua tipuri POD-struct sunt compatibile ca format (layout-compatible) daca
au acelasi numar de atribute non-statice, iar atributele non-statice
corespunzatoare (in ordine) au tipuri compatibile ca format
(layout-compatible);
- o structura POD poate contine campuri de completare fara nume
(unnamed padding);
- un pointer catre un obiect POD-struct, convertit convenabil cu
'reinterpret_cast', va pointa catre primul sau membru si invers;
aceasta implica faptul ca nu exista campuri de completare (padding)
la inceputul unei structuri POD;
'reinterpret_cast' este un instrument de conversie intre diverse tipuri
pointer, sau intre tipuri pointer si intregi;
exemplu:
struct s{int a; char b;} x;
s *p = &x;
int *q = &x.a;
if(p == reinterpret_cast<s *>(q)) cout << "OK\n"; //afisaza: OK
if(reinterpret_cast<int *>(p) == q) cout << "OK\n"; //afisaza: OK
- unei structuri POD i se poate aplica macro-ul 'offsetof':
#include<cstddef>
offsetof (type, member)
returneaza deplasamentul (distanta relativa) in bytes a membrului 'member'
in clasa/tipul structura/tipul uniune 'type'; de exemplu:
#include<iostream>
#include<cstddef>
using namespace std;
class c1{public: int a; char b;};
struct c3{int a; char b;};
union c2{int a; char b;};
int main(){
cout << offsetof(c1,b) << " "
<< offsetof(c2,b) << " "

<< offsetof(c3,b) << endl; // afisaza: 4 0 4


}
I.7. Clase imbricate si clase locale
====================================
O CLASA IMBRICATA (NESTED CLASS) este o clasa declarata in interiorul altei
clase. Numele clasei imbricate este local clasei care o include (este valid
doar in domeniul acesteia).
Intr-o clasa imbricata putem invoca nume (inclusiv de tipuri, membri,
enumerari), din clasa care o include.
Membrii unei clase imbricate nu au drepturi de acces speciale fata de
membrii altei clase imbricate in aceeasi clasa. Metodele clasei care include
nu au drepturi de acces speciale fata de membrii clasei imbricate.
Exemplu:
========
class A {
int x;
enum E {E1, E2};
class B { };
class C {
B b;
// OK
int y;
void f(A* p, int i) {
p->x = i; // OK
}
};
void g(C* p) {
C q;
// OK
E r = E1;
// OK
int z = p->y; // eroare, 'A::C::y' este privat
}
};
int main() {
B u;
// eroare, numele 'B' nu este declarat in acest domeniu (scope)
A::B v; // eroare, 'A::B' este privat
}
Putem defini atributele statice si metodele unei clase imbricate in
exteriorul claselor daca folosim nume de tip calificate - mai exact, daca
folosim specificari ale domeniilor imbricate: 'clasa1::clasa2::membru'.
Putem defini un nume cu 'typedef' pentru a reprezenta un nume de clasa
calificat. Ulterior, putem folosi numele definit cu 'typedef' impreuna cu
operatorul '::' (scope resolution) pentru a ne referi la o clasa sau membru
imbricat.
Exemplu:
========

class outside{
public:
class nested{
public:
static int x;
static int y;
int f();
int g();
};
};
int outside::nested::x = 5;
int outside::nested::f() { return 0; }
typedef outside::nested outnest; // definim un nume cu 'typedef'
int outnest::y = 10;
// folosim numele definit cu 'typedef' cu '::'
int outnest::g() { return 0; };
Observatie: Folosirea unui nume definit cu 'typedef' pentru a reprezenta
numele unei clase imbricate ascunde informatii si poate face codul mai greu
de inteles.
Un nume definit cu 'typedef' nu poate fi folosit intr-un specificator de tip
elaborat. Pentru ilustrare, in exemplul de mai sus nu putem folosi urmatoarea
declaratie:
class outnest obj;
dar putem folosi:
outnest obj;
O clasa imbricata poate mosteni alta clasa imbricata in aceeasi clasa.
Exemplu:
========
class A {
private:
class B { };
B *z;
class C : private B {
private:
B y;
A::B y2;
C *x;
A::C *x2;
};
};
Clasele imbricate sunt folosite rareori. Datorita flexibilitatii si puterii
mecanismului de mostenire, practic nu este nevoie de clase imbricate.
O CLASA LOCALA (LOCAL CLASS) este o clasa declarata in cadrul definitiei
(corpului) unei functii. Numele ei este local functiei respective (este valid
doar in domeniul acesteia).

Claselor locale li se aplica mai multe restrictii:


- Clasa locala nu poate invoca variabilele automatice ale functiei in care
este declarata; ea poate invoca nume de tipuri, enumerari, variabile statice
din domeniul functiei respective, si variabile si functii externe.
Exemplu:
========
int x;
// variabila globala
void f()
// definitia functiei
{
static int y;
// variabila statica 'y' poate fi folosita de clasa locala
int x;
// variabila automatica 'x' nu poate fi folosita de clasa locala
extern int g();
// functia externa 'g' poate fi folosita de clasa locala
class local
// clasa locala
{
int g() { return x; }
// eroare, variabila locala 'x' nu
int h() { return y; }
// valid,
int k() { return ::x; }
// valid,
int l() { return g(); }
// valid,
};

poate fi folosita de 'g'


variabila statica 'y'
'x' global
functia externa 'g'

}
int main()
{
local* z;
// eroare: numele 'local' nu este declarat in acest domeniu (scope)
}
- Metodele unei clase locale nu pot fi definite in afara declaratiei clasei
ci doar in interiorul acesteia; in consecinta, ele sunt automat functii
inline (nu mai este nevoie sa folosim cuvantul cheie 'inline').
- Clasele locale nu pot avea atribute statice.
- Functia in care este declarata o clasa locala nu au drepturi speciale de
acces la membrii acesteia.
Exemplu:
=======
void f(){
class local{
int f();
// eroare, metoda non-inline a clasei locale
int g() {return 0;} // valid, metoda inline
static int a;
// eroare, atribut static al clasei locale
int b;
// valid, atribut nonstatic
} x;
int k = x.b; // eroare, 'int f()::local::b' este privat
}
Din cauza multelor restrictii, clasele locale nu sunt folosite frecvent.

I.8. Pointeri la membri


=======================
Un POINTER LA MEMBRU este un pointer special, care indica generic catre un
membru al unei clase, nu catre o o instanta a acelui membru corespunzatoare
unui anumit obiect.
El retine doar un offeset (o pozitie sau un deplasament fata de inceputul
locatiei obiectelor, unde poate fi gasit membrul respectiv), nu o adresa
completa de memorie (a membrului in cadrul unui obiect).
Intrucat pointerii la membru nu sunt pointeri adevarati, lor nu li se pot
aplica operatorii '.' si '->'; pentru a avea acces la membrul unei clase prin
intermediul unui pointer spre el, se folosesc operatorii speciali '.*' si
'->*':
- daca 'a' este un obiect al clasei si 'b' un pointer la un membru al ei,
atunci expresia 'a.*b' inseamna: instanta membrului pointat de 'b'
corespunzatoare obiectului 'a';
- daca 'p' este un pointer la obiectele clasei si 'b' un pointer la un membru
al ei, atunci expresia 'p->*b' inseamna: instanta membrului pointat de 'b'
corespunzatoare obiectului pointat de 'p'.
Operatorii '.*' si '->*' au asociere de la stanga la dreapta si prioritate 4
(deci mai slaba decat operatorii '++', '--', '(parametri_actuali)', '[]', '.',
'->', '(tip)', '*' (unar), '&' (unar) si mai tare decat operatorii binari
'*', '/', '%', '+', '-'). De aceea trebuie sa folosim paranteze suplimentare
si sa scriem de exemplu '(a.*p)[i]' in loc de 'a.*p[i]', daca 'p' este pointer
la un membru vector (si nu vector de pointeri la membri), sau '(a.*p)(x)' in loc
de 'a.*p(x)', daca 'p' este pointer la metoda (nu functie ce returneaza pointer
la membru), si la fel in cazul lui '->*'.
Pointerii la membrii se declara asemeni pointerilor obisnuiti, dar in locul
operatorului declarativ '*' se foloseste operatorul declarativ 'clasa::*';
adresarea membrilor, in sensul offsetului, se face cu '&clasa::membru' (nu se
poate folosi '&obiect.membru', deoarece aceasta inseamna adresa de memorie);
deferentierea pointerilor la membri se face cu '.*', '->*'.
Notam urmatoarele:
- La adresarea metodelor, in sensul offsetului, nu poate fi omis '&', ca la
adresarea lor in sensul adresei de memorie.
- Membrilor (atribute, metode) statici nu li se poate determina
offsetul (evident, ei sunt independenti de obiecte, nu au offset in
cadrul obiectelor), dar li se poate determina adresa de memorie
(cu '&obiect.atribut', '&clasa::atribut', '&obiect.metoda', 'obiect.metoda',
'&clasa::metoda', 'clasa::metoda').
- Atributelor nestatice li se poate determina atat offsetul
(cu '&clasa::atribut') cat si adresa de memorie a unei instante
(cu '&obiect.atribut').
- Metodelor nestatice li se poate determina offsetul (cu '&clasa::metoda')
dar nu si adresa de memorie (cu '&obiect.metoda' sau 'obiect.metoda'),
desi corpul lor este intr-un singur exemplar in program, cu adresa de
memorie unic determinata.
Exemplu:
========
#include <iostream>

using namespace std;


class cls {
public:
int a;
void f(int);
};
void cls::f(int b) {cout << "Valoarea lui b este "<< b << endl;}
int main() {
int cls::*ptiptr = &cls::a;
// 'ptiptr' este pointer la atribute de tip 'int' ale lui 'cls'
void (cls::* ptfptr) (int) = &cls::f;
// 'ptfptr' este pointer la metode cu signatura
// 'void (cls::)(int)' ale lui 'cls'
// Obs. ca putem initializa pointerii la membri chiar daca inca nu exista
// obiecte ale clasei respective
cls ob; // 'ob' este un obiect al clasei 'cls'
ob.*ptiptr = 10;
// se initializeaza atributul 'a' al lui 'ob';
// este echivalent cu 'ob.a = 10;';
cout << "Valoarea lui a este " << ob.*ptiptr << endl;
// afisaza: Valoarea lui a este 10
(ob.*ptfptr) (20);
// se apeleaza metoda 'f' a lui 'ob';
// este echivalent cu 'ob.f(20);';
// afisaza: Valoarea lui b este 20
// parantezele au fost necesare deoarece '.*' este mai slab decat
// '()' (apelul de functie) si s-ar fi interpretat
// 'ob.*(ptfptr (20));'
}
Pentru a simplifica scrierea, putem asocia un nume cu 'typedef' tipului
pointerilor la un anumit membru al unei clase.
Exemplu: putem modifica exemplul anterior astfel:
========
typedef int cls::*my_pointer_to_member;
typedef void (cls::*my_pointer_to_function) (int);
int main() {
my_pointer_to_member ptiptr = &cls::a;
my_pointer_to_function ptfptr = &cls::f;
cls ob;
ob.*ptiptr = 10;
cout << "Valoarea lui a este " << ob.*ptiptr << endl;
(ob.*ptfptr) (20);
}
Prezentam prin comparatie cateva proprietati legate de folosirea
pointerilor normali si a pointerilor la membri, in functie de

felul membrilor: atribute/metode, statici/nestatici:


Exemplu:
=======
#include<iostream>
using namespace std;
class cls{
public:
int x;
int i; static int j;
void f(int); static void g(int);
};
int cls::j;
void cls::f(int x){cout << "f\n";}
void cls::g(int x){cout << "g\n";}
cls a;
cls *p;

// 'a' este un obiect al clasei 'cls'


// 'p' este pointer la obiecte ale clasei 'cls'

int *pv;
// 'pv' este pointer la variabile de tip 'int'
void (*pf) (int); // 'pf' este pointer la functii cu singatura 'void (int)'
int cls::*pa;
// 'pa' este pointer la atribute ale clasei 'cls' de tip 'int'
void (cls::*pm)(int);
// 'pm' este pointer la metode ale clasei 'cls' cu signatura 'void (int)'
int main(){
p = &a;
pv = &a.i;
// 'pv' ia ca valoare adresa de memorie a variabilei 'a.i';
// valoarea este de tip 'int*'
pa = &a.i;
// eroare: nu se poate converti 'int*' la 'int cls::*'
pa = &cls::i;
// 'pa' ia ca valoare offestul membrului 'i' in obiectele clasei 'cls';
// valoarea este de tip 'int cls::*'
cout << p << " " << pv << " " << pa << endl;
// afisaza: 0x804a0d8 0x804a0dc 1
// locatia lui 'a.i' este la 4 octeti dupa inceputul locatiei lui 'a'
// deoarece inaintea sa este locatia lui 'a.x'
a . i = 1;
cout << a.i << endl; // afisaza: 1
* pv = 2;
cout << a.i << endl; // afisaza: 2
a .* pa = 3; cout << a.i << endl; // afisaza: 3
p ->* pa = 4; cout << a.i << endl; // afisaza: 4
// am accesat aceeasi variabila 'a.i'
// direct, cu un pointer, cu un obiect si un pointer la membru,
// cu un pointer la obiect si un pointer la membru
pv =
//
//
pa =
//
//
//

&a.j; // echivalent cu: 'pv = &cls::j'


'pv' ia ca valoare adresa de memorie a variabilei 'cls::j';
valoarea este de tip 'int*'
&a.j; pa = &cls::j;
erori: nu se poate converti 'int*' la 'int cls::*'
deci nu se poate lua offsetul unui atribut static, ci doar adresa de
memorie a lui ca variabila (independenta de obiecte)

a . j = 5;
cout << cls::j << endl; // afisaza: 5
cls :: j = 6; cout << cls::j << endl; // afisaza: 6
* pv = 7;
cout << cls::j << endl; // afisaza: 7
// am accesat aceeasi variabila 'cls::j' (independenta de obiecte)
// direct (cu 'a.j' si, echivalent, cu 'cls::j') si cu un pointer
pf = &a.f;
// eroare: nu se poate lua adresa de memorie a unei metode nestatice
pm = &cls::f; // nu puteam zice fara '&': 'pm = cls.f;'
// 'pm' ia ca valoare offestul metodei 'f' in obiectele clasei 'cls';
// valoarea este de tip 'void (cls::*)(int)';
// nu puteam zice: 'pm = &a.f;' sau 'pm = a.f;'
cout << pm << endl; // afisaza: 1
a . f (1);
// afisaza: f
(a .* pm) (1); // afisaza: f
(p ->* pm) (1); // afisaza: f
// am apelat aceeasi functie 'a.f()'
// direct, cu un obiect si un pointer la membru,
// cu un pointer la obiect si un pointer la membru;
// la ultimele doua instructiuni parantezele au fost necesare
// deoarece '.*' si '->*' sunt mai slabi ca '()' si s-ar fi
// interpretat 'a .* (pm (1));', 'p ->* (pm (1));'
pf = &a.g; // echivalent cu: 'pf = &cls::g;', 'pf = a.g;', 'pf = cls::g;'
// 'pf' ia ca valoare adresa de memorie a functiei 'cls::g';
// valoarea este de tip 'void (*) (int)'
pm = &cls::g;
// eroare: nu se poate lua offsetul unei metode statice
cout << pf << endl; // afisaza: 1
a . g (2);
// afisaza: g
cls :: g (2); // afisaza: g
pf (2);
// echivalent cu: '(*pf)();' afisaza: g
// am apelat aceeasi functie 'a.g()'
// direct (cu 'a.g()' si, echivalent, cu 'cls::g()'),
// si cu un pointer la functie
}
Comentariu: exemplul de mai sus arata ca:
- in cazul atributelor statice:
nu se poate lua offsetul;
'&obiect.atribut' si '&clasa::atribut' inseamna adresa de memorie;
- in cazul atributelor nestatice:
'&clasa::atribut' inseamna offsetul;
'&obiect.atribut' inseamna adresa de memorie;
- in cazul metodelor statice:
nu se poate lua offsetul;
'&obiect.functie', 'obiect.functie','&clasa::functie', 'clasa::functie'
inseamna adresa de memorie;
- in cazul metodelor nestatice:
'&clasa::metoda' inseamna offsetul;
nu se poate lua adresa de memorie.
Exemplu:
========
#include<iostream>
using namespace std;
class cls{

public:
int i,j,k;
void f(int); void g(int); void h(int);
};
void cls::f(int x){cout << "f\n";}
void cls::g(int x){cout << "g\n";}
void cls::h(int x){cout << "h\n";}
int& accesare(cls *po, int cls::* pa){
return po ->* pa;
}
void aplica(cls *po, void (cls::*pm)(int), int x){
(po ->* pm)(x);
}
int main(){
int cls::*p1=&cls::i, cls::*p2=&cls::j, cls::*p3=&cls::k;
cout << p1 << " " << p2 << " " << p3 << endl;
cout << (p1 == p2) << " " << (p2 == p3) << " " << (p1 == p3) << endl;
// afisaza: 1 1 1
//
0 0 0
// deci afisarea nu arata corect valoarea pointerilor la membri,
// asa cum se intampla in cazul pointerilor obisnuiti
cls ob;
accesare(&ob, &cls::i) = 1;
cout << accesare(&ob, &cls::i) << " " << ob.i << endl; //afisaza: 1 1
// intrucat functia 'accesare' returneza referinta la 'int',
// expresia 'accesare(&ob, &cls::i)' este lvalue
aplica(&ob, &cls::f, 10); // afisaza: f
void (cls::*v[])(int) = {&cls::f, &cls::g, &cls::h};
// vector de pointeri la metode
for(int i=0; i<3; ++i) (ob.*v[i])(100);
// afisaza:
// f
// g
// h
// operatorul '.*' este mai slab decat '()', deci scrierea fara
// paranteze '(ob.*v[i])(100)' ar fi insemnat 'ob.*(v[i](100))'
}
Urmatorul exemplu ilustreaza sensurile diferite pe care le pot avea
expresiile care contin operatorii '.*' si '->*' daca adaugam sau nu paranteze
suplimentare (de exemplu '(x.*p)[i]' versus 'x.*p[i]'):
Exemplu:
========
#include<iostream>
using namespace std;
class cls{
public:
int i, j, k, v[3];
void h(int);
};

void cls::h(int x) {cout << "cls::h(" << x << ")\n";}


int cls::* p(int selector){
switch(selector){
case 0: return &cls::i;
case 1: return &cls::j;
case 2: return &cls::k;
}
}
int main(){
cls x; int i;
{
// aici, 'p' este functia ce returneaza pointer la atribut
// definita mai sus
for(i = 0; i < 3; ++i)
x.*p(i) = i*10;
cout << x.i << " " << x.j << " " << x.k << endl;
// afisaza: 0 10 20
}{
// declaram 'p' ca pointer la metoda
void (cls::* p) (int) = &cls::h;
(x.*p)(7);
// afisaza: cls::h(7)
}{
// declaram 'p' ca vector de pointeri la atribute
int cls::* p[] = {&cls::i, &cls::j, &cls::k};
for(i = 0; i < 3; ++i)
x.*p[i] = i*100;
cout << x.i << " " << x.j << " " << x.k << endl;
// afisaza: 0 100 200
}{
// declaram 'p' ca pointer la atribut-vector
int (cls::* p) [3] = &cls::v;
for(i = 0; i < 3; ++i)
(x.*p)[i] = i*1000;
cout << x.v[0] << " " << x.v[1] << " " << x.v[2] << endl;
// afisaza: 0 1000 2000
}
}
Comentariu: pe parcursul lui 'main', numele 'p' a fost folosit cu
patru sensuri diferite; pentru a nu aparea conflicte intre
declaratiile respective, le-am inclus in instructiuni compuse
separate (fiind deci locale acestora).
II. Accesibilitate, incapsulare, functii si clase prieten (friend)
==================================================================
Una dintre caracteristicie membrilor claselor/obiectelor (in particular
structurilor, uniunilor) este ACCESIBILITATEA; din acest punct de vedere
ei pot fi: PRIVATI (PRIVATE), PROTEJATI (PROTECTED), PUBLICI (PUBLIC).
Accesibilitatea membrilor se poate specifica inserand in declaratia clasei,
printre declaratiile membrilor, SPECIFICATORII DE ACCES (cuvinte cheie)
'private', 'protected', 'public' (cu sensul evident), urmati de ':'; fiecare
poate aparea de 0 sau mai multe ori.
Fiecare membru al clasei are accesibilitatea data de cea mai apropiata
specificare 'private:'/'protected:'/'public:' care ii precede declaratia;
daca declaratia membrului nu este precedata de nici o asemenea specificare,

el este (implicit) PRIVAT in cazul claselor propriuzise si PUBLIC in cazul


tipurilor structura si uniune.
Accesibilitatea limiteaza posibilitatea de a invoca membrii clasei, in felul
urmator:
- membrii privati pot fi invocati doar dintr-o metoda a clasei respective
sau dintr-o functie prietena (friend) acesteia;
- membrii protejati pot fi invocati doar dintr-o metoda a clasei respective,
dintr-o functie prietena (friend) acesteia, sau dintr-o metoda a unei clase
derivate;
- membrii publici pot fi invocati din orica pare a programului.
Observatii:
- invocarea membrului se refera doar la scrierea explicita a numelui sau,
indiferent de context - nu conteza daca numele este necalificat, calificat
cu 'obiect.', etc.
- deosebirea dintre membrii protejati si cei privati se manifesta doar cand
avem de a face cu o mostenire (detalii in lectia ***); in alte situatii,
membrii protejati se comporta ca si cei privati.
Exemplu:
========
class cls{
int a;
// atribut (implicit) privat
void f(int); // metoda (implicit) privata
public:
int b,c;
// atribute publice
void g();
// metoda publica
int d;
// atribut public
private:
private:
protected:
int e;
// atribut protejat
void h();
// metoda protejata
public:
private:
int i;
// atribut privat
public:
int j,k;
// atribute publice
};
void cls::f(int x){
cls ob, *pob = &ob;
a = x; ob.a = x; pob -> a = x; // accesare permisa
b = x; ob.b = x; pob -> b = x; // accesare permisa
e = x; ob.e = x; pob -> e = x; // accesare permisa
if(x > 0){
f(x - 1); ob.f(x - 1); pob -> f(x - 1); // accesare permisa
g();
ob.g();
pob -> g();
// accesare permisa
h();
ob.h();
pob -> h();
// accesare permisa
}
}
void cls::g(){
cls ob, *pob = &ob;
a = 0; ob.a = 0; pob -> a = 0; // accesare permisa
b = 0; ob.b = 0; pob -> b = 0; // accesare permisa
e = 0; ob.e = 0; pob -> e = 0; // accesare permisa
f(0); ob.f(0); pob -> f(0); // accesare permisa
g(); ob.g(); pob -> g(); // accesare permisa
h(); ob.h(); pob -> h(); // accesare permisa

}
void cls::h(){
cls ob, *pob = &ob;
a = 0; ob.a = 0; pob -> a =
b = 0; ob.b = 0; pob -> b =
e = 0; ob.e = 0; pob -> e =
f(0); ob.f(0); pob -> f(0);
g(); ob.g(); pob -> g();
h(); ob.h(); pob -> h();
}

0;
0;
0;
//
//
//

// accesare permisa
// accesare permisa
// accesare permisa
accesare permisa
accesare permisa
accesare permisa

int main(){
cls ob, *pob = &ob;
ob.a = 1; pob -> a = 1; // eroare ('a' este privat)
ob.b = 1; pob -> b = 1; // accesare permisa
ob.e = 1; pob -> e = 1; // eroare ('e' este protejat)
ob.f(1); pob -> f(1); // eroare ('f' este privat)
ob.g(); pob -> g(); // accesare permisa
ob.h(); pob -> h(); // eroare ('h' este protejat)
}
Comentarii:
- observam ca dintr-o metoda a clasei 'cls' (indiferent daca este privata,
protejata sau publica) putem accesa orice membru (data sau functie) al clasei
'cls' (indiferent daca este privat, protejat sau public); din 'main', care
nu este nici metoda si nici friend al clasei 'cls', nu putem accesa decat
membrii sai publici;
- dreptul de accesare are lagatura doar cu scrierea numelui membrului in
locul respectiv, nu conteaza calificarea ('ob.', 'pob->', etc.);
evident, 'main' nefiind metoda a clasei, nu are 'this' si deci nu putem
invoca membrii necalificati - de ex. nu mai merge 'b = 1' sau 'g()', dar
merge 'ob.b = 1', 'pob -> b = 1', 'ob.g()', 'pob -> g()'.
Exemplu:
========
class cls{
private:
int a;
protected: int b;
public:
int c;
};
void cls::fa(){a =
void cls::fb(){a =
void cls::fc(){a =

void fa(); // membri privati


void fb(); // membri protejati
void fc(); // membri publici
1; b = 1; b = 1;}
1; b = 1; b = 1;}
1; b = 1; b = 1;}

class clsder: public cls{


private: int d; void fd();
public: int e; void fe();
};
void clsder::fd(){
d = 1; e = 1;
a = 1; fa(); // erori, membri existenti, dar inaccesibili direct
b = 1; fb();
c = 1; fc();
}
void clsder::fe(){
d = 1; e = 1;
a = 1; fa(); // erori, membri existenti, dar inaccesibili direct
b = 1; fb();
c = 1; fc();

}
int main(){
clsder ob;
ob.a = 1; ob.fa(); // erori, membri inaccesibili
ob.b = 1; ob.fb(); // erori, membri protected
ob.c = 1; ob.fc();
ob.d = 1; ob.fd(); // erori, membri privati
ob.e = 1; ob.fe();
}
Comentarii:
- clasa 'clsder' mosteneste clasa 'cls', deci are si ea membrii 'a', 'b',
'c', 'fa', 'fb', 'fc', cu aceleasi tipuri/signaturi; in plus, are si membrii
'd', 'e', 'fd', 'fe';
- membrii 'a', 'fa', fiind privati in clasa 'cls', desi exista in clasa
'clsder', nu sunt accesibili direct, nici din metodele nou adaugate, nici din
alte functii (indiferent daca sunt sau nu friend ai lui 'cls' sau 'clsder');
ei pot fi accesati doar indirect, prin metode mostenite ramase accesibile
(de ex. 'fd' apeleaza 'fb', care a ramas accesibila si care invoca 'a');
- ceilalti membrii mosteniti din clasa 'cls', adica 'b', 'c', 'fb', 'fc',
au accesibilitatea influentata de modul cum s-a facut mostenirea (privat,
protejat sau public); intucat mostenirea s-a facut public (am scris
'public cls'), ei isi pastreaza accesibilitatea din 'cls'; in particular,
ei sunt accesibili din metodele noi 'fd', 'fe', dar numai 'c' si 'fc' sunt
accesibili si din 'main' (fiind publici).
Pentru alte detalii legate de mostenire si cum afecteaza ea accesibilitatea,
a se vedea lectia ***.
De obicei, la declararea unei clase se specifica (cel mult) cate o singura
sectiune cu membri privati, protejati sau publici. Folosirea mai multor
sectiuni cu membri de aceeasi accesibilitate are ca singur avantaj acela de
a vedea grupari ale diferitelor parti ale clasei, facilitand citirea si
intelegera programului de catre alte persoane. Pentru compilator insa, nu are
importanta folosirea mai multor specificatori de acces identici.
Exemplu:
========
class complex{
private:
double re;
public:
void setre(double);
double getre();
private:
double im;
public:
void setim(double);
double getim();
};
void complex::setre(double x) {re = x;}
double complex::getre() {return re;}
void complex::setim(double x) {im = x;}
double complex::getim() {return im;}

Constatam ca accesibilitatea nu da mai multa putere descriptiva


(expresivitate) limbajului C++ ci doar adauga niste constrangeri.
Prezenta unor caracteristici care nu cresc expresivitatea ci doar adauga
constrangeri influenteaza insa stilul de programare, facand ca programele sa
capete calitati suplimentare de software engineering (mentenabilitate,
robustete, etc.).
Mai exact, constrangerile legate de accesibilitate au urmatoarele roluri:
- Impiedicarea accesului din afara neautorizat la informatiile si mecanismele
interne ale clasei.
Exemplu:
========
#include<iostream>
#include<string.h>
#include<stdarg.h>
using namespace std;
class banca{
int nr_clienti;
struct {char nume[20], parola[20], cont;} clienti[20];
public:
banca(int, ...);
void set_cont(const char *, const char *, int);
int get_cont(const char *, const char *);
};
banca::banca(int n, ...){
va_list l;
nr_clienti = n;
va_start(l, n);
for(int i = 0; i < n; ++i){
strcpy(clienti[i].nume, va_arg(l, char *));
strcpy(clienti[i].parola, va_arg(l, char *));
clienti[i].cont = va_arg(l, int);
}
}
void banca::set_cont(const char *n, const char *p, int c){
for(int i = 0; i < nr_clienti; ++i)
if(!strcmp(n,clienti[i].nume) && !strcmp(p,clienti[i].parola)){
clienti[i].cont = c;
return;
}
}
int banca::get_cont(const char *n, const char *p){
for(int i = 0; i < nr_clienti; ++i)
if(!strcmp(n,clienti[i].nume) && !strcmp(p,clienti[i].parola))
return clienti[i].cont;
return 0;
}
banca b(3, "Ion", "123", 10, "Nelu", "456", 20, "Ana", "789", 30);
int main(){
cout << b.clienti[0].cont << ", " // eroare:
<< b.clienti[1].cont << ", " // eroare:
<< b.clienti[2].cont << endl; // eroare:
cout << b.get_cont("Nelu","124") << endl; //

membru privat
membru privat
membru privat
afisaza: 0

cout << b.get_cont("Nelu","456") << endl; // afisaza: 20


}
Comentariu: daca baza de date 'clienti' ar fi fost publica, din 'main'
s-ar fi putut consulta/modifica toate conturile; asa, un cont (ex. 'Nelu')
poate fi consultat/modificat doar daca i se cunoaste parola ('456').
- Impiedicarea initializarii obiectelor cu date invalide.
Exemplu:
========
class rational{
int numarator, numitor;
public:
void set(int, int);
int get_numarator();
int get_numitor();
};
void rational::set(int pnumarator, int pnumitor){
numarator = pnumarator;
numitor = pnumitor != 0 ? pnumitor : 1;
}
int rational::get_numarator() {return numarator;}
int rational::get_numitor() {return numitor;}
int main(){
rational r;
r.numitor = 0; // eroare: membru privat
r.set(10,0); // OK, 'r' devine numarul rational 10/1
}
Comentariu: daca atributele 'numarator', 'numitor' ar fi publici, nimeni
n-ar putea impiedica utilizatorul ('main') sa seteze numitorul cu 0,
facand ca numarul rational sa fie invalid; metoda publica 'set' va
seta numarul rational numai cu date valide.
- Daca algoritmul implementat este proiectat a.i. sa se disocieze logic partea
de comunicare cu exteriorul (intrare date, furnizare rezultate) de partea de
procesare, acesta se poate implementa sub forma unei clase a.i. partea de
comunicare sa fie inclusa in metode publice de interfata iar partea de
procesare in membri privati, iar metodele de interfata nu vor reflecta in
signatura lor detaliile mecanismelor interne de procesare.
Astfel, putem imbunatati ulterior algoritmica clasei (adaugand/eliminand/
modificand membrii privati) fara sa-i modificam interfata, anume numarul sau
signatura metodelor publice (s-ar putea modifica corpul metodelor publice,
dar acest lucru nu este vizibil pentru codul care utilizeaza clasa).
Atunci, codul existent, care utiliza vechea versiune a clasi poate fi
refolosit, fara modificari, si cu noua versiune (mai performanta) a clasei.
Exemplu: consideram o implementare a clasei 'rational', inzestrata cu o
======== metoda privata de normalizare (care simplifica fractia si face
numitorul pozitiv), a.i. orice numar rational nou calculat sa fie furnizat
in forma normalizata, si un cod (functia 'main') care o foloseste:
#include<iostream>
using namespace std;
class rational{

int numarator, numitor;


void normalize();
public:
void set(int, int);
int get_numarator();
int get_numitor();
};
void rational::normalize(){
if(numarator == 0) {numitor = 1; return;}
if(numitor < 0) {numarator = -numarator; numitor = - numitor;}
int negativ;
if(numarator < 0){negativ = 1; numarator = - numarator;}
else negativ = 0;
for(int i = 2; i <= numarator && i <= numitor; )
if(numarator % i == 0 && numitor % i == 0){
numarator /= i; numitor /= i;
}else
++i;
if(negativ) {numarator = - numarator;}
}
void rational::set(int pnumarator, int pnumitor){
numarator = pnumarator;
numitor = pnumitor != 0 ? pnumitor : 1;
normalize();
}
int rational::get_numarator() {return numarator;}
int rational::get_numitor() {return numitor;}
int main(){
rational r;
r.set(-36,-60);
cout << r.get_numarator()<< "/"<<r.get_numitor()<<endl; // afisaza: 3/5
}
Metodele 'set'/'get' din interfata nu reflecta in signatura lor normalizarea
care se aplica numerelor nou create; astfel, putem imbunatati algoritmica
interna a clasei inlocuind metoda de normalizare cu o alta, mai performanta,
care aplica algoritmul lui Euclid, fara a modifica interfata; atunci, codul
care folosea vechea clasa poate fi refolosit cu noua clasa fara modificari
(functie 'main' ramane la fel):
#include<iostream>
using namespace std;
class rational{
int numarator, numitor;
void normalize();
public:
void set(int, int);
int get_numarator();
int get_numitor();
};
void rational::normalize(){
if(numarator == 0) {numitor = 1; return;}
if(numitor < 0) {numarator = -numarator; numitor = - numitor;}
int a = numarator > 0 ? numarator : - numarator,
b = numitor;
while(a != b)
if(a > b) a -= b;
else b -= a;

numarator /= a; numitor /= b;
}
void rational::set(int pnumarator, int pnumitor){
numarator = pnumarator;
numitor = pnumitor != 0 ? pnumitor : 1;
normalize();
}
int rational::get_numarator() {return numarator;}
int rational::get_numitor() {return numitor;}
int main(){
rational r;
r.set(-36,-60);
cout << r.get_numarator()<< "/"<<r.get_numitor()<<endl; // afisaza: 3/5
}
Accesibilitatea membrilor, ca si pointerul 'this', sunt tehnici prin care
limbajul C++ implementeaza caracteristica de INCAPSULARE, specifica OOP;
ca si concept de programare, incapsularea presupune doua aspecte (a se vedea
lectia 1):
- o constructie de limbaj ce faciliteaza impachetarea datelor cu operatiile
definite asupra lor, creandu-se legaturi stranse intre ele;
in C++ aceasta cerinta este atinsa prin faptul ca putem grupa date si cod
in obiecte, care apoi sunt manevrate ca un tot unitar, ca si cand ar fi
niste date obisnuite; membrii unui obiect sunt mai strans legati intre ei
decat datele/functiile independente, deoarece de exemplu metodele unui
obiect pot accesa membrii aceluiasi obiect fara specificari suplimentare,
prin utilizarea implicita a pointerului 'this' (functiilor independente
trebuie sa le comunicam explicit toate informatiile);
- un mecanism de limbaj pentru restrictionarea accesului la unele
componente ale pachetului;
in C++ aceasta cerinta este atinsa prin folosirea diverselor tipuri de
accesibilitate: privat, protejat, public.
In general, stilul de programare OOP cere sa respectam PRINCIPIUL
INCAPSULARII DATELOR: atributele sa fie private, iar accesul la ele sa
fie permis doar prin metode publice.
Exemple: clasele 'banca' si 'rational' din exemplele precedente respecta
aceasta cerinta; prezentam alte doua exemple:
Exemplu:
========
#include<iostream>
using namespace std;
class complex{
double re, im;
public:
void read();
void write();
complex operator+(complex);
};
void complex::read(){cin >> re >> im;}
void complex::write(){cout << re << "+i*" << im << endl;}
complex complex::operator+(complex b){
complex c;

c.re = re + b.re; c.im = im + b.im;


return c;
}
int main(){
complex x, y, z;
x.read(); y.read();
z = x + y;
z.write();
}
Comentariu: in clasa 'complex' am integrat cateva operatii uzuale: citire,
scriere, adunare; in general, este bine sa existe cat mai multe operatii,
pentru a face clasa utila in cat mai multe aplicatii; in felul acesta insa,
dimensiunea clasei creste si, oricum, nu putem epuiza toate operatiile
posibile.
Exemplu:
========
#include<iostream>
using namespace std;
// nivelul 1
class complex{
double re, im;
public:
void setre(double);
void setim(double);
double getre();
double getim();
};
void complex::setre(double x) {re = x;}
void complex::setim(double x) {im = x;}
double complex::getre(){return re;}
double complex::getim(){return im;}
// nivelul 2
void read(complex &c){
double x;
cin >> x; c.setre(x);
cin >> x; c.setim(x);
}
void write(complex c){
cout << c.getre() << "+i*" << c.getim() << endl;
}
complex operator+(complex a, complex b){
complex c;
c.setre(a.getre() + b.getre()); c.setim(a.getim() + b.getim());
return c;
}
// nivelul 3
int main(){
complex x, y, z;
read(x); read(y);
z = x + y;
write(z);
}

Comentarii:
- Am rescris clasa 'complex' din exemplul precedent a.i. interfata
ei sa contina metode putine, dar foarte flexibile, a.i. prin combinarea lor
sa poate fi valorificata toata functionalitatea clasei: 'setre', 'setim',
'getre', getim'; operatiile cu numere complexe 'read', 'write', '+' au fost
scrise deasupra acestor metode, considerate ca primitive (operatii
elementare) - ele nu trecut de aceasta granita, ca sa acceseze direct 're',
'im'; functia 'main', care rezolva o problema de adunare, a fost scrisa
desupra acestor operatii.
- Codul este organizat astfel pe trei niveluri, fiecare avand o interfata
proprie (formata din operatii definite pe nivelul respectiv) si accesand
nivelurile inferioare doar prin interfata lor.
Acest stil de organizare a codului pe niveluri il face mai usor de
intretinut, reutilizat, depanat (putem face modificari pe un nivel fara sa
afectam celelalte niveluri, putem localiza mai usor o eroare) dar mai putin
performant (de exemplu, in functia 'operator+' instructiunea
'c.setre(a.getre() + b.getre());' face mai multe operatii decat ar fi facut
'c.re = a.re + b.re;').
Implementarea atributelor ca publice nu este in concordanta cu stilul de
programare OOP, dar uneori este utila - de exemplu, dorim ca un anumit atribut
des folosit sa fie accesibil direct (nu mediat de apelul unor metode), pentru
a obtine timpi de rulare mai buni.
TODO: exemplu
Putem permite unei functii (in particular unui operator) care nu este metoda
a unei clase sa poata invoca membrii privati si protejati ai clasei, declarand
functia respectiva ca FUNCTIE PRIETEN (FRIEND FUNCTION) a clasei; in acest
scop, scriem in cadrul declaratiei clasei (nu conteaza in care loc) prototipul
functiei precedat de cuvantul cheie 'friend'. Functia prietena va putea invoca
toate numele declarate in clasa respectiva, inclusiv nume de tipuri sau
constante ale tipurilor enumerare.
Atentie: o functie independenta declarata ca friend a unei clase nu devine
metoda a clasei respective; in consecinta:
- nu are parametrul ascuns 'this'; deci, ea nu poate invoca membrii clasei
careia ii este friend fara calificare (cu 'obiect.' sau 'clasa::', in cazul
membrilor statici).
- daca este un operator, trebuie declarat cu un numar de parametri egal cu
aritatea (nu cu aritatea - 1).
Exemplu:
========
class complex{
double re, im;
public:
void setre(double);
void setim(double);
double getre();
double getim();
friend void read(complex &);
};
void complex::setre(double x) {re = x;}
void complex::setim(double x) {im = x;}
double complex::getre(){return re;}
double complex::getim(){return im;}
void read(complex &c){cin >> c.re >> c.im;}

void write(complex c){cout << c.getre() << "+i*" << c.getim() << endl;}
Comentarii:
- Functia 'read' este prietena a clasei 'complex' si de aceea,
desi nu este metoda a ei, poate invoca mambrii 're' si 'im' (care sunt
privati); astfel, ea acceseaza atributele lui 'c' mai rapid decat functia
'write', care le acceseaza prin intermediul metodelor 'get'.
- In interiorul lui 'read' nu putem invoca membrii clasei 'complex' fara
calificare (de ex. 're' in loc de 'c.re') deoarece 'read' n-are 'this'.
Exemplu:
========
#include<iostream>
using namespace std;
class cls{
enum tip {val1, val2} n;
friend void f(cls);
public:
cls();
};
cls::cls(){n = val1;}
void f(cls x){
cls::tip v;
v = x.n;
if (v == cls::val1) cout << "OK\n";
}
int main(){
cls ob;
f(ob); // afisaza: OK
}
Comentariu: functia friend 'f' a clasei 'cls' a putut invoca atat membrul sau
privat 'n' cat si tipul 'tip' si constanta 'val1'.
Functiile friend se dovedesc utile atunci cand mai multe clase contin membri
corelati cu alte sectiuni ale programului si am vrea ca de acolo sa-i putem
accesa pe toti simultan, printr-un singur apel de functie, nu prin apelarea
succesiva a unor metode publice din fiecare clasa.
Exemplu:
=======
class cls2;
class cls1{
int stare;
public:
void ocupa();
void elibereaza();
friend int inactiv(cls1, cls2);
};
void cls1::ocupa(){
// ocupa o resursa comuna
stare = 1;
}
void cls1::elibereaza(){

// elibereaza resursa comuna


stare = 0;
}
class cls2{
int stare;
public:
void ocupa();
void elibereaza();
friend int inactiv(cls1, cls2);
};
void cls2::ocupa(){
// ocupa resursa comuna
stare = 1;
}
void cls2::elibereaza(){
// elibereaza resursa comuna
stare = 0;
}
int inactiv(cls1 c1, cls2 c2){
return c1.stare == 0 && c2.stare == 0;
}
int main(){
cls1 x; cls2 y;
x.ocupa(); y.ocupa();
if(inactiv(x,y)){
// administreaza resursa
}
x.elibereaza();
if(inactiv(x,y)){
// administreaza resursa
}
}
Comentariu: am presupus existenta unei resurse comune pe care o pot ocupa
obiectele 'x', 'y' si care poate fi administrata din 'main' doar cand acestea
sunt ambele inactive; respectand principiul incapsularii datelor si in
absenta unui friend, de fiecare data in locul unui singur apel de functie
'inactiv(x,y)' ar fi trebuit sa avem doua, de forma
'x.inactiv() && y.inactiv()', ceea ce ar fi marit timpul de rulare a
programului.
Folosirea functiilor friend nu este in concordanta cu stilul de programare
obiectual, unde se doreste a se lucra doar cu clase si metode (nu cu tipuri
care nu sunt clase si functii independente) si unde dorim sa avem incapsulare
(datele unui obiect sa poata fi accesate doar prin interfata sa formata din
metode publice, nu prin functii care nu sunt metode ale clasei). Uneori insa
functiile friend nu pot fi evitate fara a modifica artificial codul.
Exemplu:
========
#include<iostream>
using namespace std;
class complex{
double re, im;
public:

friend istream& operator>>(istream&, complex&);


friend ostream& operator<<(ostream&, complex);
complex operator+(complex); // adunare complex + complex
complex operator+(double); // adunare complex + double
friend complex operator+(double, complex); // adunare double+complex
};
complex complex::operator+(complex b){
complex c;
c.re = re + b.re; c.im = im + b.im;
return c;
}
complex complex::operator+(double b){
complex c;
c.re = re + b; c.im = im;
return c;
}
complex operator+(double a, complex b){
complex c;
c.re = a + b.re; c.im = b.im;
return c;
}
istream& operator>>(istream& s, complex& c){
s >> c.re >> c.im;
return s;
}
ostream& operator<<(ostream& s, complex c){
s << c.re << "+i*" << c.im;
return s;
}
int main(){
complex x, y, z1, z2, z3; double a, b;
cin >> x >> y >> a >> b;;
z1 = x + y; z2 = x + a; z3 = b + x;
cout << z1 << ", " << z2 << ", " << z3 << endl;
}
Comentariu: Observam ca operatorul '+' intre 'double' si 'complex' nu poate fi
definit ca metoda, deoarece ar fi metoda a lui 'double', care nu este clasa
si, oricum, este un tip predefinit (nu poate fi modificat); similar, '>>' si
'<<' ar trebui adaugate ca metode claselor 'istream', 'ostream', care insa
sunt predefinite (nu pot fi modificate).
Putem transforma fortat codul de mai sus a.i. sa nu lucreze decat cu clase
si metode si sa respecte incapsularea datelor, in felul urmator (exercitiu !):
- in loc sa lucram direct cu 'double', scriem o clasa 'real' cu un membru
'double' si in care sa declaram ca metoda operatorul 'real + complex';
- cream clasele 'intrare', 'iesire', care mostenesc 'istream', respectiv
'ostream', si in care declaram ca metode operatorii 'intrare >> real',
'intrare >> complex', respectiv 'iesire << real', 'iesire << complex',
iar in loc de 'cin', 'cout' vom lucra cu doua instante ale noilor clase.
Intrucat functiile friend constituie o incalcare a principiului incapsularii,
este recomandat sa le folosim doar cand este strict necesar.
Putem declara ca friend a unei clase si o metoda a altei clase (nu a
aceleiasi clase).
Evident, aceasta functie va avea parametrul ascuns 'this', dar in legatura
cu clasa din care face parte ca metoda, nu cu clasa careia ii este friend;
asadar, ca si in cazul functiilor independente, ea nu va putea invoca

membrii clasei careia ii este friend fara calificare (cu 'obiect.' sau
'clasa::', in cazul membrilor statici).
Exemplu:
========
#include<iostream>
using namespace std;
class cls1;
class cls2{
public:
void g(int, cls1&);
};
class cls1{
int n;
static int s;
public:
void set(int);
int get();
friend void f(cls1);
friend void cls2::g(int, cls1&);
};
int cls1::s = 0;
void cls1::set(int x){n = x;}
int cls1::get(){return n;}
void cls2::g(int x, cls1 &y){
y.n = x;
cls1::s = x;
}
void f(cls1 x){
cout << x.n << " " << cls1::s << endl;
}
int main(){
cls1 ob1; cls2 ob2;
ob2.g(10, ob1);
f(ob1); // afisaza: 10 10
}
Comentarii:
- Clasa 'cls1' are ca functii friend pe 'f' (functie independenta) si pe 'g'
(metoda a clasei 'cls2'); ambele pot accesa membrii privati ai clasei
'cls1', 'n' (membru nestatic) si 's' (membru static); deoarece 'f' si 'g'
nu au parametru 'this' in legatura cu obiecte ale clasei 'cls1' ('g' are
'this' legat de clasa 'cls2' si care la apelul 'ob2.g(10, ob1)' primeste
ca valoare '&ob2') si atunci in aceste functii 'n' si 's' nu pot fi invocate
decat calificate (am calificat 'n' cu 'y.' si 's' cu 'cls1::').
- Atentie la ordinea in care am scris declaratiile: declaratia incompleta
(vida) 'class cls1' trebuie sa preceada declaratia completa (cu membri) a
clasei 'cls2', altfel nu putem declara in interior 'void g(int, cls1&);';
declaratia completa a clasei 'cls2' trebuie sa preceada declaratia
completa a clasei 'cls1', altfel nu putem declara in interior
'friend void cls2::g(int, cls1&);'; declaratiile complete ale claselor

'cls1' si 'cls2' trebuie sa preceada definitiile (cu corp) ale functiilor,


altfel nu putem invoca in corpurile acestora membrii claselor.
Putem declara o clasa ca fiind CLASA PRIETEN (FRIEND CLASS) a altei clase;
in acest scop, scriem in cadrul declaratiei celei de-a doua clase (nu conteaza
in care loc) declaratia incompleta (vida) a primei clase precedate de cuvantul
cheie 'friend'.
Atunci din interiorul primei clase se pot invoca toate numele declarate in
cea de-a doua clasa; in particular, toate metodele primei clase vor fi functii
prietene ale celei de-a doua clase; in plus, din prima clasa se pot invoca si
nume de tipuri sau constante ale tipurilor enumerare definite in a doua clasa.
Exemplu:
========
#include<iostream>
using namespace std;
class pion;
class sah{
enum culori{alb, negru};
pion *tabla[8][8];
public:
sah();
void afisaza();
friend class pion;
};
class pion{
sah::culori k;
// culoare
int s, l, c;
// sensul de avans vertical, linie, coloana
public:
static sah joc;
// toti pionii sunt pe o aceeasi tabla
pion(int, int, int);
void actioneaza();
~pion();
char tip();
};
sah::sah(){
for(int i = 0; i < 8; ++i)
for(int j = 0; j < 8; ++j)
tabla[i][j] = NULL;
}
void sah::afisaza(){
for(int i = 7; i >= 0; --i){
for(int j = 0; j <= 7; ++j)
if(tabla[i][j] == NULL) cout << ".";
else cout << tabla[i][j] -> tip();
cout<<endl;
}
}
sah pion::joc;

// se creaza tabla comuna, apeland implicit


// constructorul 'sah()'
pion::pion(int pk, int pl, int pc){ // culoare, linie, coloana
k = pk == 1 ? sah::alb : sah::negru;

s = pk == 1 ? 1 : -1;
l = pl; c = pc;
joc.tabla[l][c] = this;
}
void pion::actioneaza(){ // muta sau captureaza
int x, y;
x = l + s; y = c - 1;
do{
if(0 <= x && x <= 7 && 0 <= y && y <= 7
&& joc.tabla[x][y] != NULL
&& joc.tabla[x][y] -> k != k)
{delete joc.tabla[x][y]; break;}
x = l + s; y = c + 1;
if(0 <= x && x <= 7 && 0 <= y && y <= 7
&& joc.tabla[x][y] != NULL
&& joc.tabla[x][y] -> k != k)
{delete joc.tabla[x][y]; break;}
x = l + s; y = c;
if(0 <= x && x <= 7 && joc.tabla[x][y] == NULL)
break;
return;
}while(0); // do-while(0) + break este o alternativa la goto
joc.tabla[x][y] = this;
joc.tabla[l][c] = NULL;
l = x; c = y;
}
pion::~pion(){joc.tabla[l][c] = NULL;}
char pion::tip(){return k == sah::alb ? 'A' : 'N';}
int main(){
pion *p1 = new pion(1,1,4), *p2 = new pion(-1,4,3);
pion::joc.afisaza(); cout << endl;
p1 -> actioneaza(); pion::joc.afisaza(); cout << endl;
p2 -> actioneaza(); pion::joc.afisaza(); cout << endl;
p1 -> actioneaza(); pion::joc.afisaza(); cout << endl;
}
Comentariu: clasa 'pion' este prietena a clasei 'sah', deci din interiorul
ei am putut invoca atat membrul privat 'tabla' cat si numele de tip 'culori'
si constantele 'alb', 'negru'; clasa 'sah' nu este insa automat prietena a
clasei 'pion' (relatia de prietenie nu este simetrica), deci din interiorul
ei nu putem invoca membrul privat 'k', astfel ca trebuie sa folosim metoda
publica 'tip'; membrul 'joc' al lui 'pion' este static si public, deci poate
fi invocat din 'main' cu 'pion::joc'.
Caracteristici ale relatiei de prietenie (friendship):
- Prieteniea nu este o relatie simetrica: daca clasa 'A' este friend a
clasei 'B', clasa 'B' nu este automat friend a clasei 'A'.
- Prietenia nu este tranzitiva: daca functia 'f' sau clasa 'A' sunt friend
ale clasei 'B' si clasa 'B' este friend a clasei 'C', atunci functia 'f',
respectiv clasa 'A', nu sunt automat friend ale clasei 'C'.
- Prietenia nu se mosteneste:
* o functie sau clasa friend a clasei de baza nu este automat friend a
clasei derivate; la fel si viceversa;

* daca clasa de baza este friend a altei clase, clasa derivata nu este
automat friend a acelei clase; la fel si viceversa;
- Accesul in baza prieteniei se mosteneste: un friend al clasei derivate
poate invoca membrii mosteniti din clasa de baza, dar numai pe aceia care
sunt accesibili direct in metodele noi adaugate (i.e. membrii care in
clasa de baza nu au fost privati)
Alte restrictii legate de functiile friend:
- O functie friend nu poate fi declarata 'static' sau 'extern'.
III. Membri statici
===================
O instanta a unei clase consta dintr-o colectie de instante pentru membrii
clasei.
Putem declara un membru (atribut sau metoda) a unei clase ca fiind STATIC,
daca precedam declaratia sa cu cuvantul cheie 'static'; altfel, este NESTATIC.
- Un membru nestatic este asociat unei instante a clasei (obiect);
in particular:
* un atribut nestatic are instante diferite (locatii diferite) in obiecte
diferite; aceste instante apar odata cu obiectele respective, deci
definitia atributului este parte a definitiei obiectelor;
declaratia atributului in cadrul declaratiei clasei nu poate contine
initializari;
* o metoda nestatica are parametrul ascuns 'this', prin care se poate referi
la obiectul care a apelat-o si la membrii sai;
* membrii nestatici pot fi invocati fara precizarea obiectului doar din
metodele nestatice ale aceleiasi clase;
intr-o metoda nestatica putem invoca fara precizarea obiectului orice
membru al clasei respective (iar el va fi calificat implicit cu 'this->',
deci va fi considerat de la obiectul apelant);putem invoca de asemenea
direct numele tipurilor si constantelor de enumerare si ale altor tipuri
incuibate in clasa.
- Un membru static este asociat clasei, dar nu unei instante (obiect) anume el este comun tuturor instantelor clasei; in particular:
* un atribut static are o instanta comuna (o aceeasi locatie) pentru toate
obiectele clasei; aceasta instanta apare inainte de a exista obiecte ale
clasei, astfel incat atributul necesita o definitie separata, scrisa in
afara declaratiei clasei (aceasta definitie fiind in afara domeniului
clasei, numele membrului trebuie invocat cu precizarea domeniului
'clasa::'); definitia trebuie sa fie globala la nivelul fisierului
(file scope);
declaratia atributului in cadrul declaratiei clasei poate contine
initializari doar daca atributul este si 'const'; definitia exterioara
a atributului poate contine initializari; in absenta initializarii, el
este initializat cu o valoare implicita (ex. 0 pentru numere);
* o metoda statica nu are parametrul ascuns 'this';
* membrii statici pot fi invocati fara precizarea obiectului din orice
metoda a clasei si din functiile care nu sunt metode ale acesteia, in
ultimul caz trebuind insa precizat domeniul lor cu 'clasa::';
intr-o metoda statica putem invoca fara precizarea obiectului doar
membrii statici ai aceleiasi clase; putem invoca de asemenea direct
numele tipurilor si constantelor de enumerare si ale altor tipuri
incuibate in clasa.

Notam:
- Invocarea membrilor cu precizarea unui obiect (eventual si a domeniului
'clasa::') nu este influentata de calitatea lor de a fi statici/nestatici
ci doar de regulile generale de accesibilitate (a se vedea capitolul II din
aceasta lectie).
- atributele statice se pot accesa inainte de a exista obiecte ale clasei;
astfel, le putem asigna valori care sa fie folosite ulterior de toate
obiectele;
metodele statice se pot apela inainte de a exista obiecte ale clasei; ele
pot fi folosite pentru a initializa atributele statice si pentru a face
setari/initializari comune la nivelul tipului de date (clasei);
- Intrucat atributele statice au o aceeasi locatie pentru toate obiectele
clasei, prin intermediul lor se pot comunica informatii intre obiecte
diferite, fiind astfel un substitut in stilul OOP pentru variabilele
globale.
- Asa cum am spus in capitolul I din aceasta lectie, corpul unei metode
(indiferent daca este statica su nestatica) se gaseste intr-un singur
exemplar in program si toate apelarile metodei de catre diverse obiecte
provoaca saltul la el (deci la o aceeasi adresa); metodele nestatice au
insa si parametrul ascuns 'this', prin care sunt informate despre obiectul
apelant si pot face referire la membrii lui.
Exemplu:
========
#include<iostream>
using namespace std;
class cls{
public:
int i;
static int j;
void f(); static void g();
void ns();
static void s();
};
int cls::j;
void cls::f(){}
void cls::g(){}
void cls::ns(){
cls a;
i = 1; cls::i = 2; this -> i = 3; this -> cls::i = 4;
f(); cls::f(); this -> f(); this -> cls::f();
// 'i' si 'f' ale obiectului curent
j = 10; cls::j = 20; this -> j = 30; this -> cls::j = 40;
g(); cls::g(); this -> g(); this -> cls::g();
// 'j' si 'g' comune tuturor obiectelor lui 'cls';
a.i = 100; a.cls::i = 200; a.f(); a.cls::f();
// 'i' si 'f' ale obiectului 'a'
a.j = 1000; a.cls::j = 2000; a.g(); a.cls::g();
// 'j' si 'g' comune tuturor obiectelor lui 'cls'
cout
<< i << " " << cls::i << " " << this -> i << " " << this -> cls::i << " "
<< j << " " << cls::j << " " << this -> j << " " << this -> cls::j << " "

<< a.i << " " << a.cls::i << " " << a.j << " " << a.cls::j << endl;
// afisaza: 4 4 4 4 2000 2000 2000 2000 200 200 2000 2000
// deoarece 'i' are locatii diferite pentru obiectul curent si 'a'
// iar 'j' are aceeasi locatie pentru obiectul curent si 'a'
}
void cls::s(){
cls a;
i = 11; cls::i = 22; f(); cls::f();
// erori: 'i' si 'f' sunt nestatici (deci depind de obiect)
// iar 's' este statica (deci nu are un obiect curent)
this -> i = 33; this -> cls::i = 44; this -> f(); this -> cls::f();
this -> j = 55; this -> cls::j = 66; this -> g(); this -> cls::g();
// erori: 's' este statica, deci nu are 'this'
j = 77; cls::j = 88; g(); cls::g();
// 'j' si 'g' comune tuturor obiectelor lui 'cls';
// fiind statice (deci nu depind de obiect), se pot invoca din metode
// statice (care n-au obiect curent) fara precizarea unui obiect
a.i = 110; a.cls::i = 220; a.f(); a.cls::f();
// 'i' si 'f' ale obiectului 'a'
a.j = 330; a.cls::j = 440; a.g(); a.cls::g();
// 'j' si 'g' comune tuturor obiectelor lui 'cls'
cout
<< j << " " << cls::j << " "
<< a.i << " " << a.cls::i << " " << a.j << " " << a.cls::j << endl;
// afisaza: 440 440 220 220 440 440
// deoarece 'i' are locatii diferite pentru obiectul curent si 'a'
// iar 'j' are aceeasi locatie pentru obiectul curent si 'a'
}
int main(){
cls b, x;
i = 111; f(); j = 222; g();
// erori: 'i', 'j', 'f', 'g' sunt nume necunoscute in acest
// domeniu (scope)
cls::i = 333; cls::f();
// erori: 'i' si 'f' sunt nestatici (deci depind de obiect)
// iar 'main' nu este metoda a lui 'cls' (deci nu are un obiect
// al lui 'cls' curent)
this -> i = 444; this -> cls::i = 555; this -> f(); this -> cls::f();
this -> j = 666; this -> cls::j = 777; this -> g(); this -> cls::g();
// erori: 'main' nu este metoda a lui 'cls', deci nu are 'this'
cls::j = 888; cls::g();
// 'j' si 'g' comune tuturor obiectelor lui 'cls';
// fiind statice (deci nu depind de obiect), se pot invoca din functii
// ne-metode ale lui 'cls' (care n-au un obiect al lui 'cls' curent)
// fara precizarea unui obiect, dar trebuie precizat domeniul lor,
// 'cls::'
b.i = 1110;
// 'i' si
b.j = 3330;
// 'j' si

b.cls::i = 2220; b.f(); b.cls::f();


'f' ale obiectului 'b'
b.cls::j = 4440; b.g(); b.cls::g();
'g' comune tuturor obiectelor lui 'cls'

x.ns(); x.s();

cout
<< cls::j << " "
<< b.i << " " << b.cls::i << " " << b.j << " " << b.cls::j << endl;
// afisaza: 440 2220 2220 440 440
// deoarece 'i' are locatii diferite pentru obiectele 'a' din functii
// si 'b' din main, iar 'j' are aceeasi locatie pentru obiectele 'a'
// din functii si 'b' din main
}
Un atribut static declarat intr-o clasa are o acceasi instanta (locatie)
pentru toate obiectele, atat ale clasei sale cat si ale claselor derivate
din ea. De aceea, el nu mai trebuie definit (in afara clasei) pentru clasele
derivate.
Exemplu:
========
#include<iostream>
using namespace std;
class baz{
public:
int i; static int j;
void f(int);
};
// 'baz' are:
// - un 'i', cu locatii diferite pentru obiecte diferite;
// - un 'j', cu locatii comune pentru obiecte diferite;
int baz::j;
void baz::f(int n){
i = 1*n; baz::i = 2*n;
j = 3*n; baz::j = 4*n;
}
class der: public baz{
public:
int i; static int j;
void g(int);
};
// 'der' are:
// - doi 'i' (unul mostenit, unul nou); fiecare are locatii
//
diferite pentru obiecte diferite; 'i' mostenit si 'i' nou
//
au locatii diferite pentru un acelasi obiect;
// - doi 'j' (unul mostenit, unul nou); fiecare are locatii
//
comune pentru obiecte diferite; locatia comuna a lui 'j'
//
mostenit si locatia comuna a lui 'j' nou sunt insa
//
locatii diferite; locatia lui 'j' mostenit, comuna obiectelor
//
lui 'der', si locatia lui 'j' cel declarat in clasa 'baz',
//
comuna obiectelor lui 'baz', coincid;
int der::baz::j; // eroare, acest 'j' a mai fost definit o data
int der::j; // acesta este 'j' cel nou, care n-a mai fost definit
void der::g(int n){
i = 1*n; der::i = 2*n; // 'i' nou, al obiectului curent
j = 3*n; der::j = 4*n; // 'j' nou, comun obiectelor lui 'der'
baz::i = 5*n; der::baz::i = 6*n; // 'i' mostenit, al obiectului curent

baz::j = 7*n; der::baz::j = 8*n; // 'j' mostenit, comun obiectelor lui


// 'der' si obiectelor lui 'baz'
}
int main(){
baz ob1, ob2; der od1, od2;
ob1.f(1); ob2.f(10); od1.g(100); od2.g(1000);
cout << ob1.i << " " << ob2.i << " "
<< od1.i << " " << od1.baz::i << " "
<< od2.i << " " << od2.baz::i << " "
<< baz::j << " " << der::j << " " << der::baz::j << endl;
// afisaza: 2 20 200 600 2000 6000 8000 4000 8000
// intr-adevar, in program avem 8 locatii diferite de tip 'int':
// - 'ob1.i', 'ob2.i', 'od1.i', 'od1.baz::i', 'od2.i', 'od2.baz::i',
// 6 locatii diferite;
// - 'ob1.j', 'ob2.j', 'od1.baz::j', 'od2.baz::j', locatii identice;
// - 'od1.j', 'od2.j', locatii identice;
}
Exemplu:
========
#include<iostream>
using namespace std;
class baz{
public:
int i; static int j;
};
// 'baz' are:
// - un 'i', cu locatii diferite pentru obiecte diferite;
// - un 'j', cu locatii comune pentru obiecte diferite;
class der: public baz{
public:
static int i; int j;
};
// 'der' are:
// - doi 'i' (unul mostenit, unul nou);
//
'i' mostenit are locatii diferite in obiecte diferite
//
(indiferent daca sunt obiecte ale lui 'baz' sau ale lui 'der');
//
'i' nou are locatii comune pentru obiectele lui'der' si nu este
//
prezent in obiectele lui 'baz';
// - doi 'j' (unul mostenit, unul nou);
//
'j' mostenit are locatii comune pentru obiectele lui 'der'; aceasta
//
locatie si locatia lui 'j' cel declarat in clasa 'baz', comuna
//
obiectelor lui 'baz', coincid;
//
'j' nou are locatii diferite pentru obiecte ale lui 'der' diferite si
//
nu este prezent in obiectele lui 'baz';
int baz::j, der::i; // definitiile membrilor statici
int main(){
baz b1, b2; der d1, d2;
b1.i = 1; b1.j = 2; b2.i = 3; b2.j = 4;
d1.baz::i = 10; d1.i = 20; d1.baz::j = 30; d1.j = 40;
d2.baz::i = 10; d2.i = 20; d2.baz::j = 30; d2.j = 40;
cout << b1.i << " " << b1.j << " " << b2.i << " " << b2.j << " "

<< d1.baz::i << " " << d1.i << " " << d1.baz::j << " " << d1.j << " "
<< d2.baz::i << " " << d2.i << " " << d2.baz::j << " " << d2.j << " "
<< endl;
//afisaza: 1 30 3 30 10 20 30 40 10 20 30 40
}
Comentariu: in programul de mai sus exista urmatoarele instante ale membrilor
'i', 'j' din cele doua clase:
- 'baz::i': 'b1.i', 'b2.i', doua locatii diferite;
- 'baz::j' echivalent cu 'der::baz::j':
'b1.j', 'b2.j', 'd1.baz::j', 'd2.baz::j', locatii identice;
- 'der::baz::i': 'd1.baz::i', 'd2.baz::i', doua locatii diferite;
- 'der::i': 'd1.i', 'd2.i', locatii identice;
- 'der::j': 'd1.j', 'd2.j', doua locatii diferite;
Cand definim un atribut static cu initializare, initializatorul este
considerat in zona de accesibilitate a clasei in care am declarat atributul;
astfel, desi expresia respectiva este scrisa in afara declaratiei clasei, in
ea putem invoca membrii clasei indiferent daca sunt publici, privati sau
protejati; invocarea membrilor trebuie insa facuta cu precizarea unui obiect
sau al clasei 'clasa::' (in cazul membrilor statici), deoarece suntem in
domeniul de accesibilitate al clasei, dar nu in domeniul ei de nume.
Exemplu:
========
class C {
static int i;
static int j;
static int k;
static int l;
static int m;
static int n;
static int p;
static int q;
static int r;
static int s;
static int f() { return 0; }
int a;
public:
C() { a = 0; }
};
C c;
int C::i
int C::j
int C::k
int C::l
int C::s
int C::r

=
=
=
=
=
=

C::f();
C::i;
c.f();
c.j;
c.a;
1;

//
//
//
//
//
//

initializare
initializare
initializare
initializare
initializare
initializare

class Y : private C {} y;
int
int
int
int

C::m
C::n
C::p
C::q

Comentarii:

=
=
=
=

Y::f();
Y::r;
y.r;
y.f();

//
//
//
//

eroare
eroare
eroare
eroare

cu
cu
cu
cu
cu
cu

o metoda statica
un alt atribut static
o metoda a unui obiect
un atribut al unui obiect
un atribut nestatic al unui obiect
o valoare constanta

- In expresiile de initializare 'C::f()', 'C::i', 'c.f()', 'c.j', 'c.a'


sunt invocati membri privati ai clasei 'C', desi ele nu sunt scrise
intr-o metoda a lui 'C'.
- Cele patru erori din final apar ca urmare a mostenirii private; daca
scriam 'class Y : public C {} y', atunci membrii lui 'Y' erau accesibili
membrilor lui 'C' si nu se mai semnala eroare.
Daca un atribut static este de un tip intreg 'const' sau enumerare 'const',
se poate specifica un initializator constant chiar in declaratia membrului din
cadrul declaratiei clasei. Acest initializator constant trebuie sa fie o
expresie care produce o valoare intreaga constanta. Initializatorul constant
nu este o definitie, atributul trebuie in continuare definit in afara clasei
cu 'clasa::'.
Nota: Teste efectuate cu gcc/g++ versiunea 4.5.1 20101208 sub Linux au
aratat ca tipul atributului poate fi si altul decat intreg sau enumerare
(ex. 'const double'), iar daca am specificat un initializator in declaratia
din clasa, putem omite definitia exterioara; nu putem insa specifica
initializatori si in declaratie si in definitie.
Atributele statice 'const' TREBUIE sa aiba initializare (fie indeclaratia
din clasa, fie in definitia exterioara).
Exemplu:
========
#include<iostream>
using namespace std;
class c0{public: static const int n;};
const int c0::n;
// eroare: constanta neinitializata 'c0::n'
class c1{public: static const int n = 1;};
const int c1::n;
class c2{public: static const int n;};
const int c2::n = 2;
class c3{public: static const int n = 3;};
const int c3::n = 3;
// eroare: initializare duplicata pentru 'c3::n'
class c4{public: static const int n = 4;};
class c{public: static const double n;};
const double c::n = 3.14;
int main() {
cout << c1::n << " " << c2::n << " " << c4::n << " " << c::n << " " << endl;
// afisaza: 1 2 4 3.14
}
Una dintre utilizarile obisnuite ale membrilor statici este de a asigura
controlul accesului mai multor obiecte la unele resurse comune. Metodele
statice se pot apela chiar inainte de a exista obiecte ale clasei respective
si pot face initializari ale atributelor statice.
Exemplu:
========

#include<iostream>
#include<fstream> // pentru 'std::fstream' si operatii cu fisiere
#include<stdlib.h> // pentru 'srand', 'rand'
#include<time.h>
// pentru 'time'
using namespace std;
class scriitori{
static fstream f;
// permite operarea asupra unui fisier
static bool ok;
// true/false = se poate/nu se poate scrie
static scriitori * ocupant; // cine detine fisierul; NULL = nimeni
char nume;
// caracterul scris de un scriitor la o scriere
public:
static void init(const char *fisier);
static void final();
scriitori(char);
bool ocupa();
// incearca sa ocupe fisierul; 1/0 = succes/esec
bool scrie();
// incearca sa isi scrie numele o data; 1/0 = succes/esec
bool elibereaza(); // incearca sa elibereaza fisierul; 1/0 = succes/esec
};
fstream scriitori::f; bool scriitori::ok; scriitori *scriitori::ocupant;
void scriitori::init(const char *fisier){
f.open (fisier, fstream::out); // deschide fisierul 'fisier' in scriere
ok = ! f.fail();
// se poate scrie d.d. s-a deschis cu succes
ocupant = NULL;
}
void scriitori:: final(){
f.close(); // inchide fisierul
ok = false; // dupa inchiserea fisierului nu se mai poate scrie
}
scriitori::scriitori(char pnume): nume(pnume) {}
bool scriitori::ocupa(){
if(!ok || ocupant) return false;
ocupant = this; return true;
}
bool scriitori::scrie(){
if(!ok || ocupant != this) return false;
f << nume << " ";
return ! f.fail();
}
bool scriitori::elibereaza(){
if(ocupant != this) return false;
ocupant = NULL; return true;
}
int main(){
scriitori::init("f.txt");
scriitori s1('A'), s2('B'), s3('C');

const char *ns[] = {"s1", "s2", "s3"};


// vector cu numele variabilelor-obiect
const char *na[] = {"ocupe fisierul", "scrie", " elibereze"};
// vector cu numele activitatilor desfasurate de metode
scriitori *s[] = {&s1, &s2, &s3};
// vector de pointeri la obiecte ale clasei 'scriitori',
// initializat cu adresele obiectelor 's1', 's2', 's3'
bool (scriitori::*p[]) ()
= {&scriitori::ocupa, &scriitori::scrie, &scriitori::elibereaza};
// vector de pointeri la metode ale clasei 'scriitori',
// initializat cu adresele metodelor 'ocupa', 'scrie', 'elibereaza'
int i,j; // variabile de lucru
srand(time(NULL));
// initializeaza generatorul de nr. pseudoaleatoare furnizate de 'rand'
// cu seed-ul dat de momentul curent, furnizat de 'time' ca nr. de
// secunde care au trecut de la 1 ianuarie 1970, ora 00:00 UTC
for(int k = 0; k < 10; ++k){
i = rand() % 3; j = rand() % 3;
if((s[i] ->* p[j]) ())cout << ns[i] << " reuseste sa " << na[j] << endl;
else cout << ns[i] << " incearca sa " << na[j] << endl;
}
// 'rand' genereaza urmatorul nr. pseudoaleator din secventa,
// in intervalul 0 .. RAND_MAX
scriitori::final();
}
La rulare, o evolutie posibila este:
s2
s2
s2
s1
s3
s1
s1
s3
s1
s2

reuseste
reuseste
reuseste
reuseste
incearca
reuseste
reuseste
incearca
reuseste
reuseste

sa
sa
sa
sa
sa
sa
sa
sa
sa
sa

ocupe fisierul
scrie
elibereze
ocupe fisierul
elibereze
scrie
scrie
scrie
elibereze
ocupe fisierul

In acest caz, fisierul 'f.txt' va contine:


B A A
Alte utilizari frecvente ale membrilor statici (exercitiu: scrieti programe
demonstrative in acest sens):
- asignarea unui ID unic fiecarui nou obiect al clasei:
in acest scop, clasa are un atribut static 'getid' initializat cu 0,
si un atribut nestatic 'id' (acesta va avea cate o valoare diferita
pentru fiecare obiect); constructorul clasei va efectua: 'id = ++getid';
- numararea instantelor curente ale clasei:
in acest scop, clasa are un atribut static 'count', constructorul va
efectua '++count' iar destructorul va efectua '--count';
constructorul poate fi scris a.i. daca 'count' era 0 sa aloce niste resurse
comune, care vor fi folosite ulterior de toate obiectele, si sa
initializeze aceste resurse, iar destructorul poate fi scris a.i. daca

'count' devine 0 sa dezaloce resursele respective.


Alte caracteristici si restrictii legate de membrii statici:
- Atributele statice pot avea o singura definitie nivelul programului, scrisa
global, intr-unul din fisierele sursa; ele au 'external linkage';
astfel, daca scriem definitia lor intr-un fisier header (alaturi de
declaratia clasei) si includem acest fisier (cu '#include') de mai multe
ori in program (eventual in fisiere sursa diferite), definitia va
aparea de mai multe ori la nivelul programului, conducand la o eroare de
linkeditare;
metodele statice au, de asemenea, 'external linkage'.
- Clasele fara nume (ex. uniuni anonime), clasele continute in clase
anonime si clasele locale nu pot avea atribute statice.
- Un atribut static poate fi de orice tip in afara de 'void' sau 'void'
calificat cu 'const' sau 'volatile'.
- Un atribut static nu poate fi declarat cu 'mutable'.
- Metodele statice nu pot fi declarate 'virtual' (deoarece n-au 'this');
- Intr-o clasa nu poate exista o versiune statica si una nestatica pentru
un acelasi membru.
IV. Instantierea claselor si a membrilor, membri obiect
=======================================================
Instantierea unei clase presupune instantierea membrilor sai:
- Metodele (statice si nestatice), atunci cand nu sunt 'inline', au cate o
singura copie in program, iar toate apelurile cu/fara precizarea obiectului
sunt directionate catre aceste copii;
Daca sunt 'inline', ele sunt expandate la locul fiecarui apel (fiecare
apel este inlocuit de compilator cu o copie adaptata a corpului).
- Un atribut static descrie o variabila independenta de obiectele clasei,
doar ca numele sau este declarat in domeniul acesteia, astfel ca in afara
ei trebuie invocat cu specificarea domeniului 'clasa::'.
El se instantiaza ca o variabila unica pentru clasa sa, independenta de
obiectele clasei (exista chiar si daca clasa nu are obiecte) si de aceea
necesita o definitie proprie, exterioara declaratiei clasei si independenta
de definitiile obiectelor ei. Aceasta definitie este similara unei definitii
obisnuite de variabila (doar ca numele atributului trebuie prefixat cu
'clasa::'), si trebuie sa fie globala.
Ca urmare, atributul static are clasa de alocare (storage class) statica,
adica este creat la inceputul executiei programului (inainte de executarea
lui 'main'), este distrus la sfarsitul executiei programului (dupa
terminarea lui 'main'), iar pe toata perioada existentei sale are o aceeasi
locatie; in plus, este accesibil si din alte fisiere ale programului
('external linkage').
Daca intr-un fisier al programului sunt definite mai multe atribute
statice, ele sunt create in ordinea definirii si distruse in ordinea
inversa crearii.
- Un atribut nestatic descrie o variabila care este parte a unui obiect al
clasei.

El se instantiaza la fiecare instantiere a clasei, instanta sa fiind parte


a obiectului respectiv; de aceea, definitia sa este parte a definitiilor
obiectelor.
O instanta a unui atribut nestatic are aceeasi clasa de alocare (storage
class) ca si obiectul in care a fost creata: statica, automatica, dinamica,
etc.
La crearea unui obiect, instantele membrilor sai nestatici sunt create in
ordinea in care au fost declarati in clasa; la distrugerea obiectului,
ei sunt distrusi in ordinea inversa crearii lor.
Discutia de mai sus este valabila si in cazul atributelor care sunt masive
(vectori, matrici, etc.), doar ca in acest caz numele atributului desemneaza o
adresa, nu o locatie.
Atributele pot fi non-obiecte (de ex. 'int', 'double', etc.) sau obiecte (de
diverse clase).
Instantierea unui atribut non-obiect presupune doar alocarea unei locatii
si initializarea cu o valoare; aceasta valoare poate fi una implicita sau
una specificata in cadrul definitiei atributului:
- In cazul atributelor non-obiect statice, valoarea initiala se poate
specifica printr-o expresie de initializare scrisa in definitia exterioara
clasei:
<Tip> <Nume clasa>::<Nume atribut> = <Expresie>;
Daca expresia lipseste:
<Tip> <Nume clasa>::<Nume atribut>;
atributul static primeste implicit valoarea nula.
- In cazul atributelor non-obiect nestatice, valoarea initiala se poate
specifica printr-o expresie de initializare in cadrul unei liste de clauze
de initializare scrisa in definitia obiectului (daca este o structura
agregat):
<Nume clasa> <Nume obiect> = {<Expresie 1>, ..., <Expresie n>};
sau in listele de initializatori ale constructorilor clasei:
<Nume clasa>::<Nume clasa> (<Parametri formali>):
<Initializator 1>, ..., <Initializator atribut>, ...,<Initializator n>
{<corp>}
unde <Initializator atribut> poate fi:
<Nume atribut>(<Expresie>)
Obiectul poate fi definit fara lista de clauze de initializare:
<Nume clasa> <Nume obiect>;
De asemenea, unii dintre constructorii clasei pot sa nu aiba initializatori
pentru toate atributele nestatice:
<Nume clasa>::<Nume clasa> (<Parametri formali>):
<Initializator i1>, ..., <Initializator in>
{<corp>}

(unde <i1>, ..., <in> nu acopera toate atributele nestatice).


Cand obiectul este creat cu o definitie ce nu specifica o valoare pentru
un anumit atribut nestatic al sau, acesta va primi o valoare implicita in
conformitate cu clasa de alocare a obiectului: daca obiectul este static, va
avea valoare nula; daca obiectul este automatic, va avea valoarea aflata pe
stiva in locul unde a fost amplasata locatia.
Instantierea unui atribut obiect presupune atat alocarea unei locatii cat si
apelarea unui constructor al clasei obiectului; acesta poate fi constructorul
implicit (i.e. fara parametri sau cu toti parametrii impliciti), daca nu
specificam nici unul, sau un constructor specificat in cadrul definitiei
atributului.
- In cazul atributelor obiect statice, constructorul atributului se poate
specifica printr-o lista de parametri actuali intre paranteze rotunde scrisa
in definitia exterioara clasei:
<Tip> <Nume clasa>::<Nume atribut> (<Parametri actuali>);
(constructorul atributului va fi ales de compilator in functie de
corespondenta intre tipurile parametrilor actuali si tipurile parametrilor
formali, dupa regulile generale de la supraincarcarea functiilor).
Daca exista un singur parametru actual, se poate scrie echivalent:
<Tip> <Nume clasa>::<Nume atribut> = <Parametru actual>;
Daca lista de parametri actuali intre paranteze rotunde lipseste:
<Tip> <Nume clasa>::<Nume atribut>;
atunci se va folosi automat constructorul implicit al clasei atributului.
- In cazul atributelor obiect nestatice, constructorul atributului se poate
specifica printr-un initializator constand din numele acestuia si o lista de
parametri actuali intre paranteze rotunde, scris in listele de initializatori
ale constructorilor clasei gazda (alegerea constructorului atributului va fi
facuta de compilator dupa aceleasi reguli de corespondenta a tipurilor):
<Nume clasa gazda>::<Nume clasa gazda> (<Parametri formali>):
<Initializator 1>, ..., <Initializator atribut>, ...,<Initializator n>
{<corp>}
unde <Initializator atribut> poate fi:
<Nume atribut> (<Parametri actuali>)
sau poate lipsi, si atunci pentru atributul respectiv se va folosi
constructorul implicit.
Constructori diferiti ai clasei gazda pot avea initializatori diferiti
pentru un acelasi atribut.
La crearea unui obiect al clasei gazda se apeleaza un constructor al sau,
iar acesta va apela pentru atributele obiectului constructorii din lista
sa de initializatori.
Exemplu:
========

#include<iostream>
using namespace std;
class cls1{
public:
int i, j;
static int s,t;
};
int cls1::s, cls1::t = 1; // 'cls1::s' primeste implicit valoarea 0
cls1 a, b = {10, 20};
// 'a' si 'b' sunt stocat static;
// 'a.i','a.j' primesc implicit valoarea 0 ('a' fiind stocat static);
// 'b.i' primeste valoarea 10, 'b.j' primeste valoarea 20;
class cls2{
public:
int i,j;
cls2(int);
cls2(int, int);
};
cls2::cls2(int p): i(100), j(200) {}
cls2::cls2(int p, int q): i(1000) {} // 'j' primeste o valoare implicita
// puteam scrie: cls2::cls2(int p, int q) {i = 1000;}
// si atunci mai intai 'i' si 'j' ar fi primit o valoare implicita,
// apoi 'i' devenea 1000 (iar 'j' ramanea cu valoarea implicita)
cls2 c(123), d(123, 456);
// 'c' si 'd' sunt stocate static;
// lui 'c' i se aplica constructorul 'cls2(int)', deci 'c.i' primeste
// valoarea 100 iar 'c.j' primeste valoarea 200
// lui 'd' i se aplica constructorul 'cls2(int, int)', deci 'c.i' primeste
// valoarea 1000 iar 'c.j' primeste implicit valoarea 0 ('d' fiind
// stocat static)
int main(){
cls1 x;
// 'x' este stocat automatic; ca atare, in lipsa clauzelor de
// initializare, 'x.i' si 'x.j' au implicit valoarea aflata pe
// stiva in momentul alocarii
cls2 y(12, 34);
// 'y' este stocat automatic si i se aplica constructorul
// 'cls2(int ,int)'; ca atare, 'y.i' primeste valoarea 10000
// iar 'y.j' are implicit valoarea aflata pe stiva in momentul alocarii
cout << cls1::s << " " <<
<< a.i << " " << a.j
<< b.i << " " << b.j
<< x.i << " " << x.j
<< c.i << " " << c.j
<< d.i << " " << d.j
<< y.i << " " << y.j
// afisaza: 0 1 0 0 10 20
}
Exemplu:
========
#include<iostream>

cls1::t << " "


<< " "
<< " "
<< " "
<< " "
<< " "
<< endl;
-1218168325 -1216888060 100 200 1000 0 1000 0

using namespace std;


class clsob{
public:
int n;
clsob();
clsob(int);
clsob(int, int);
};
clsob::clsob(): n(1) {}
clsob::clsob(int p): n(p) {}
clsob::clsob(int p, int q) {} // 'n' primeste o valoare implicita
class cls{
public:
clsob x, y;
cls();
cls(int);
cls(int, int);
};
cls::cls(): x(10), y(20, 30) {}
cls::cls(int p): x(40) {}
// echivalent cu a scrie:
// cls::cls(int p): x(40), y() {}
cls::cls(int p, int q) {}
// echivalent cu a scrie:
// cls::cls(int p): x(), y() {}
cls
//
//
//
//
//
//
//
//
//
//
//
//

a, b(100), c(200, 300);


'a', 'b', 'c' sunt stocate static;
pentru 'a' se aplica constructorul 'cls()',
deci pentru 'a.x' se aplica 'clsob(10)', a.i. 'a.x.n' devine 10,
iar pentru 'a.y' se aplica 'clsob(20,30)' a.i. 'a.y.n' devine 0
('a' fiind stocat static)
pentru 'b' se aplica constructorul 'cls(100)',
deci pentru 'b.x' se aplica 'clsob(40)', a.i. 'b.x.n' devine 40,
iar pentru 'b.y' se aplica 'clsob()' a.i. 'a.y.n' devine 1
('a' fiind stocat static)
pentru 'c' se aplica constructorul 'cls(200, 300)',
deci pentru 'c.x' se aplica 'clsob()', a.i. 'c.x.n' devine 1,
iar pentru 'c.y' se aplica 'clsob()' a.i. 'c.y.n' devine 1

int main(){
cout << a.x.n <<
<< b.x.n <<
<< c.x.n <<
// afisaza: 10 0
}

" " <<


" " <<
" " <<
40 1 1

a.y.n << " "


b.y.n << " "
c.y.n << endl;
1

Notam ca daca intr-o clasa nu declaram explicit nici un constructor,


compilatorul adauga unul implicit (fara parametri); daca declaram explicit
constructori definiti de utilizator, compilatorul nu va mai genera
constructorul respectiv ca un constructor separat ci va integra codul sau in
constructorii definiti de utilizator - de aceea, clasa nu va mai avea un
constructor implicit (i.e. fara parametri sau cu toti parametrii impliciti)
decat daca declaram explicit unul. Pentru alte detalii, a se vedea lectia ***.
Exemplu:

========
#include<iostream>
using namespace std;
class clsob{public: int k; clsob(int); ~clsob();};
clsob::clsob(int n): k(n) {cout << "Constructor " << k << endl;}
clsob::~clsob() {cout << "Destructor " << k << endl;}
class cls2{public: static clsob n2;};
class cls1{public: static clsob n1;};
clsob a(0);
clsob cls1::n1(1);
clsob cls2::n2(2);
class cls3{
public:
clsob i,j;
cls3(int);
cls3(int, int);
};
cls3::cls3(int p): i(10), j(20) {}
cls3::cls3(int p, int q): j(200), i(100) {}
int main(){
cout << "Incepe main\n";
clsob b(11);
cls3 c(11), d(22, 33);
}
Programul afisaza:
Constructor 0
Constructor 1
Constructor 2
Incepe main
Constructor 11
Constructor 10
Constructor 20
Constructor 100
Constructor 200
Destructor 200
Destructor 100
Destructor 20
Destructor 10
Destructor 11
Destructor 2
Destructor 1
Destructor 0
Comentarii:
- Variabilele stocate static se creaza in ordinea in care au fost definite:
intai 'a', apoi 'cls1::n1', apoi 'cls1::n2' (chiar daca clasele lui 'n1'
si 'n2' au fost declarate in ordine inversa); crearea lor se face inainte
de a se executa 'main'; ele se distrug dupa terminarea lui 'main' in
ordinea inversa crearii.

- Atributele nestatice ale lui 'cls3' se instantiaza la crearea obiectelor


'b' si 'c' in ordinea declararii lor in clasa: intai 'i', apoi 'j',
indiferent de ordinea in care au fost invocati in lista de initializatori
ale constructorilor; ele se distrug la distrugerea obiectelor gazda,
in ordinea inversa crearii.
V. Membri 'const' si atribute 'mutable'
=======================================
V.1. Constante, pointeri la zone constante, pointeri constanti
==============================================================
O CONSTANTA este o data read-only (se poate consulta, nu se poate modifica);
ea se poate declara ca o variabila obisnuita, masiv, etc., a carui tip de
baza este insotit de cuvantul cheie 'const' ('const tip' sau 'tip const').
In legatura cu o constanta sunt permise doar operatiile de consultare, nu si
cele de modificare (ex. '++').
De aceea, constanta este initializata la crearea sa (prin definitia sa) si
va pastra valoarea respectiva pe toata durata existentei sale; in particular:
- Daca constanta este non-obiect, definitia sa trebuie sa contina o
initializare de forma:
<Tip> const <Nume_constanta> = <Valoare>;
sau:
<Tip> const <Nume_constanta>(<Valoare>);
si atunci constanta va avea valoarea respctiva; initializarea nu poate lipsi.
- Daca constanta este obiect (deci la creare i se aplica automat un
constructor), definitia sa poate fi insotita de o liste de clauze de
initializare intre '{}' (daca clasa obiectului este o clasa agregat):
<Nume clasa> const <Nume_constanta> = {<Expresie 1>, ..., <Expresie n>};
(clauzele se refera la atributele nestatice) sau de o lista de parametri
actuali intre '()' care sa permita selectarea constructorului (dupa regulile
generale de la supraincarcarea functiilor):
<Nume clasa> const <Nume_constanta> (<Parametri actuali>);
sau echivalent, cand exista un singur parametru actual:
<Nume clasa> const <Nume_constanta> = <Parametru actual>;
Initializarea poate lipsi:
<Nume clasa> const <Nume_constanta>;
doar daca clasa are un constructor implicit (i.e. fara parametri sau cu toti
parametrii impliciti) definit de utilizator (cel generat automat de
compilator nu este suficient).
- daca constanta este parametru formal (non-obiect sau obiect), atunci

initializarea este optionala (daca este prezenta, avem un parametru


implicit); intr-adevar, in acest caz constanta se instantiaza la fiecare
apel al functiei, initializandu-se cu parametrul actual furnizat.
Exemplu:
========
#include<iostream>
using namespace std;
int const a = 1;
// se poate scrie echivalent:
// 'const int a = 1;', 'int const a(1);', 'const int a(1);'
const int b; // eroare, constanta neinitializata
int const c[] = {10, 20, 30}, d[3] = {40, 50}, e[3];
// 'c' si 'd' sunt OK,
// la 'e' se semnaleaza eroare, constanta neinitializata
void f(int const x){
x = 0; // eroare, 'x' este read-only
cout << x << endl;
}
int main(){
int i; // 'i' este non 'const'
a = 2; // eroare, 'a' este read-only
i = 3; // OK
cout << c[0] << " " << c[1] << " " << c[2] << endl
<< d[0] << " " << d[1] << " " << d[2] << endl;
// afisaza: 10 20 30
//
40 50 0
f(a); f(i);
// afisaza:
// 1
// 3
}
Comentariu: componenta lui 'd' ramasa fara de clauza de initializare
corespunzatoare (adica 'd[2]') a primit valoarea implicita conform
clasei de alocare a lui 'd', anume 0 ('d' are storage class static).
Exemplu:
========
#include<iostream>
using namespace std;
class cls1{
public:
int i, j;
};
// clasa agregat,
// se poate instantia cu clauze de initializare intre '{}'
cls1 const a;
// eroare, constanta neinitializata
// intr-adevar, nu exista clauze intre '{}' iar clasa nu are constructor
// implicit definit de utilizator (are un constructor implicit generat

// automat de compilator, dar nu este suficient)


cls1 const b = {2, 3}, c = {4};
// OK, exista clauze intre '{}';
// 'b.i' si 'b.j' primesc valorile date explicit 2, respectiv 3;
// 'c.i' primeste valoarea data explicit 4,
// 'c.j' primeste valoarea implicita conform clasei de alocare a lui 'd',
//
anume 0 ('c' are storage class static)
cls1 const d[] = {{5},{6, 7},{8}}, e[3] = {{9, 10}}, f[3] = {};
// OK, se definesc 9 obiecte, atributele lor nestatice pentru care nu
// exista clauze de initializare primesc valoarea implicita 0 (cele 9
// obiecte au storage class static)
cls1 const g[3];
// eroare, constanta neinitializata,
// deoarece nu exista nici lista de clauze de initializare intre '{}',
// nici un constructor implicit definit de utilizator in 'cls1'
// (exista unul implicit generat automat de compilator, dar nu este
// suficient)
class cls2{
public:
int n;
cls2();
cls2(int);
};
// clasa non agregat,
// nu se poate instantia cu clauze de initializare intre '{}'
cls2::cls2(){n = 1;}
cls2::cls2(int x){n = x;}
cls2 const h, x(11), y[3];
// OK in toate cele trei cazuri;
// pentru 'h' si componentele lui 'y' se foloseste constructorul implicit
// 'cls2()' definit de utilizator (care asigneaza atributul 'n' cu
// valoarea 1), iar pentru 'x' constructorul 'cls2(int)'
int main(){
c.i = 100; e[1].i = 200; x.n = 300; y[1].n = 400;
// erori, 'c.i', 'e[1].i', 'x.n', 'y[1].n' sunt read-only
int k;
cout << b.i << " " << b.j << endl
<< c.i << " " << c.j << endl;
// afisaza: 2 3
//
4 0
for(k = 0; k < 3; ++ k) cout << d[k].i << " " << d[k].j << endl;
// afisaza: 5 0
//
6 7
//
8 0
for(k = 0; k < 3; ++ k) cout << e[k].i << " " << e[k].j << endl;
// afisaza: 9 10
//
0 0
//
0 0
for(k = 0; k < 3; ++ k) cout << f[k].i << " " << f[k].j << endl;
// afisaza: 0 0
//
0 0
//
0 0
cout << h.n << endl << x.n << endl;
// afisaza: 1
//
11
for(k = 0; k < 3; ++ k) cout << y[k].n << endl;
// afisaza: 1

//
//

1
1

}
Comentarii:
- acest exemplu ne arata ca in definitia obiectelor/masivelor de obiecte
constante:
* daca exista lista de clauze de initializare intre '{}', chiar daca ea
nu specifica valori pentru toate atributele nestatice si/sau toate
obiectele, nu se semnaleaza eroare iar atributele nestatice omise primesc
valoarea implicita conform storage class-ului obiectelor;
* daca nu exista lista de clauze de initializare intre '{}' dar exista
in clasa un constructor implicit definit de utilizator, nu se semnaleaza
eroare iar pentru obiectul/componentele masivului de obiecte respectiv
se foloseste acest constructor;
* eroare se semnaleaza daca nici nu exista lista de clauze de initializare
intre '{}', nici nu exista in clasa un constructor implicit definit de
utilizator.
- daca defineam constructorul implicit al clasei 'cls2' astfel:
cls2::cls2(){}
(cu alte cuvinte nu precizam pe nici o cale valoarea lui 'n'), atributul
'n' al obiectelor 'h', 'y[0]', 'y[1]', 'y[2]' ramanea cu valoarea
implicita conform clasei de alocare a lui 'h' si 'y', anume 0 ('h' si 'y'
au storage class static); daca in clasa 'cls2' eliminam cu totul
constructorul implicit definit de utilizator, la 'h' si 'y' se semnala
eroare (constanta neinitializata).
Exemplu:
=======
#include<iostream>
using namespace std;
class cls{
public:
static int i;
int j;
};
int cls::i = 1;
int main(){
cls const ob = {10};
// clauzele de initializare se refera la atributele nestatice
cout << ob.i << " " << ob.j << endl; // afisaza: 1 10
ob.i = 2; // OK, chiar daca 'ob' este constant, 'i' este alta variabila
ob.j = 20; // eroare, 'ob.j' este read-only
cout << ob.i << " " << ob.j << endl; // afisaza: 2 10
}
Comentarii:
- Clauzele de initializare intre '{}' privesc atributele nestatice; cele
statice, care sunt variabile de sine statatoare, sunt initializate separat,
in definitia lor.
- Declararea unui obiect cu 'const' face read-only doar partea sa nestatica.
Asa cum vom vedea in sectiunea V.3., unui obiect constant nu i se pot apela

decat metode constante.


Un POINTER CATRE ZONA CONSTANTA este un pointer avand ca tip de baza
un tip de constante (adica tipul de baza este specificat 'const tip' sau
'tip const'). Lui i se pot asigna diverse valori-adresa, ale unor zone
ce nu sunt neaparat 'const' (i.e. compilatorul accepta conversia de la
'tip *' la 'tip const *'), dar prin intermediul pointerului (cu '*' sau
'->') zonele respective vor putea fi accesate doar in mod read-only.
Exemplu:
========
#include<iostream>
using namespace std;
int *p; int const * q, *r[5];
// 'p' este pointer la 'int'
// 'q' este pointer la zone constante 'int'
// 'r' este vector de pointeri la zone constante 'int'
// ('[]' este prioritar fata de '*')
int a;
// non constanta
int const b = 1; // constanta
void f(int const *x){
cout << *x << endl;
*x = 60;
// eroare, 'x' va putea accesa locatia lui 'a' doar in mod read-only
x = &b;
}
int main(){
p = &a; *p = 10; cout << a << endl; // afisaza: 10
p = &b;
// eroare, nu este permisa conversia de la 'int const *' la 'int *'
q = &a;
// OK, este permisa conversia de la 'int *' la 'int
// dar 'q' va putea accesa locatia lui 'a' doar in
a = 20; // OK
*q = 30; // eroare, 'q' acceseaza zonele doar in mod
cout << *q << endl; // afisaza: 20
q = &b;
b = 40; // eroare, 'b' este read-only
*q = 50; // eroare, 'q' acceseaza zonele doar in mod
cout << *q << endl;
// afisaza: 1

const*',
mod read-only
read-only

read-only

r[2] = &b;
cout << *r[2] << endl; // afisaza: 1
// '[]' este prioritar fata de '*', deci nu sunt necesare paranteze
// suplimentare
f(&a);// afisaza: 20
}
Comentariu: constatam ca lui 'q', 'r[2]', 'x' le putem schimba valoarea, dar
zonei accesate prin '*q', '*r[2]', '*x' nu; daca zona accesata nu este ea
insasi declarata ca o constanta, ii putem schimba valoarea, insa pe alta
cale decat prin intermediul pointerului constant (de exemplu a mers

'a = 20;', dar nu si '*q = 30;', desi era vorba de aceeasi zona).
Un POINTER CONSTANT este un pointer read-only (se poate consulta, nu se poate
modifica); el se poate declara ca un pointer obisnuit, folosind insa
operatorul declarativ '* const' in loc de '*'; el este similar constantelor
tratate mai sus, in particular trebuie initializat in definitia sa, iar
ulterior nu i se poate schimba valoarea (pointeaza o aceeasi zona pe toata
durata existentei sale).
Zona pointata de un pointer constant nu este neaparat read-only (decat daca
l-am definit ca pointer constant catre zona constanta), cu alte cuvinte desi
nu puteam schimba valoarea pointerului (nu il putem face sa pointeze o alta
zona), putem modifica zona pointata prin intermediul lui.
Exemplu:
========
#include<iostream>
using namespace std;
int a, b;
// non constante
int const c = 1; // constanta
int * const p, * const v[3];
// erori, constante neinitializate
int * const q = &c;
// eroare, nu este permisa conversia de la 'const int*' (adresa lui 'c')
// la 'int*' (tipul constantei 'q')
int * const r = &a, * const w[3] = {&a, &b};
// 'r' este pointer constant la 'int', initializat cu adresa lui 'a'
// 'w' este vector de pointeri constanti la 'int', ale carui componente
// sunt initializate cu respectiv adresa lui 'a', adresa lui 'b', NULL
// (ultima fiind valoare implicita pentru storage class-ul static)
void f(int * const x){
x = &a; // eroare, 'x' este read-only
*x = 10;
cout << *x << endl;
}
int main(){
r = &a;
// eroare, 'r' este read-only
*r = 20;
// 'a' primeste valoarea 20
w[1] = &a; // eroare, 'w[1]' este read-only
*w[1] = 30; // 'b' primeste valoarea 30
cout << a << " " << b << " "
<< *r << " " << *w[0] << " " << *w[1] << endl;
// afisaza: 20 30 20 20 30
cout << *w[2] << endl;
// la executare poate provoaca terminarea anormala a programului,
// deoarece 'w[2]' are valoarea NULL
f(&a); // afisaza: 10
f(&b); // afisaza: 10
}
Comentariu: constatam ca lui 'r', 'w[1]', 'x' nu le putem schimba valoarea,
dar zonei accesate prin '*r', '*w[1]', '*x' da (deoarece sunt pointeri
constanti catre zone neconstante).

Urmatorul exemplu trateaza pointerii constanti catre zone constante (i.e.


nici pointerului nici zonei prin intermediul lui, nu le putem schimba
valoarea):
Exemplu:
========
#include<iostream>
using namespace std;
int a;
// non constanta
int const b = 1; // constanta
int const * const p = &a, * const q = &b;
// 'p' si 'q' sunt pointeri constanti catre zone constante;
// fiind in particular constante, 'p' si 'q' trebuie initializati
// in definitie; initializarea lui 'p' cu '&a' este permisa,
// deoarece este permisa conversia de la 'int*' la 'int const*'
// (conversia inversa nu este permisa)
int const * const r; // eroare, constanta neinitializata
void f(int const * const x){
x = &a; // eroare, 'x' este read-only
*x = 10; // eroare 'x' acceseaza zonele doar in mod read-only
cout << *x << endl;
}
int main(){
p = &a; // eroare, 'p' este read-only
*p = 10; // eroare 'p' acceseaza zonele doar in mod read-only
a = 20; // OK, 'a' nu este constanta
cout << a << " " << *p << endl; // afisaza: 20 20
q = &a; // eroare, 'q' este read-only
*q = 10; // eroare 'p' acceseaza zonele doar in mod read-only
b = 20; // eroare, 'b' este read-only
cout << b << " " << *q << endl; // afisaza: 1 1
f(&a); // afisaza: 20
f(&b); // afisaza: 1
}
Toate consideratiile de mai sus se extind asemanator si in cazul cand
in loc de pointeri avem pointeri la membru.
Exemplu:
========
class cls{
public:
int i;
const int j;
cls();
};
cls::cls():j(1){}
int cls::* p;

// 'p' este pointer la membru al clasei 'cls' de tip 'int'


int const cls::* q;
// 'q' este pointer la membru al clasei 'cls' de tip 'int' constant
int cls::* const r = &cls::i;
// 'r' este pointer constant la membru al clasei 'cls' de tip 'int';
// in particular, trebuie initializat la definire si nu merge cu
// '&cls::j' (nu este permisa conversia de la 'const int cls::*'
// la 'int cls::*'
int const cls::* const t = &cls::j;
// 'r' este pointer constant la membru al clasei 'cls' de tip 'int'
// constant;
// in particular, trebuie initializat la definire si merge si cu
// '&cls::i' (este permisa conversia de la 'int cls::*' la
// 'const int cls::*')
Pentru a intelege mai usor o declaratie in care apare de mai multe ori
'*' si 'const', reamintim urmatoarele reguli de formare si citire a unei
declaratii, valabila si in limbajul C:
- o declaratie are forma:
<Tip> <Expresie declarativa 1>, ..., <Expresie declarativa n>;
(daca dorim definitii, expresiile declarative pot fi insotite de
initializatori);
- fiecare <Expresie declarativa> declara un singur nume, folosind operatorii
declarativi unari '*' (pointer, se pune in stanga operandului),
'[dimensiune]' (vector, se pune in dreapta operandului), '(parametri)'
(functie, se pune in dreapta operandului);
ordinea prioritatilor acestora este: '*' < '[]' < '()';
se pot folosi paranteze pentru a modifica ordinea implicita de asociere pe
baza prioritatilor;
- declaratia se citeste/interpreteaza concentric, de la numele declarat spre
exterior, aplicand operatorii declarativi in ordinea intalnirii; daca se
intalnesc doi simultan (unul in stanga si unul in dreapta) se aplica in
ordinea descrescatoare a prioritatilor;
- nu sunt permise: vector de functii, functie ce returneaza vectori, functie
ce returneaza functii;
- nu este permisa mixarea definitiilor de functii (antet + corp) cu alte
declaratii/definitii; putem mixa insa declaratii de functii (doar prototip)
cu alte declaratii/definitii;
Un caz particular de 'Tip' este cel de forma 'Tip const'; cazuri particulare
de '*' (pointeri) sunt cele de forma '* const', 'clasa::*', 'clasa::* const'.
Exemplu:
========
int
int
int
int
int

*a;
//
const *a;
//
* const a = &x;
//
const * const a = &x; //
* const a[3] = {},
(* const b)[3] = NULL,
* const c(int, char),

'a'
'a'
'a'
'a'

is
is
is
is

a
a
a
a

pointer to 'int'
pointer to constant 'int'
constant pointer to 'int'
constant pointer to constant 'int'

(* const d)(int, char) = NULL;


// 'a' este vector de 3 pointeri constanti la intreg neconstant,
// 'b' este pointer constant la vector de 3 intregi neconstanti,
// 'c' este functie ce primeste un 'int' si un 'char' si returneaza un
// pointer constant catre intreg neconstant (aici 'c' este doar
// declarat, ca prototip, nu definit, cu antet si corp),
// 'd' este pointer constant la functie ce primeste un 'int' si un 'char'
// si returneaza un intreg neconstant
int const * const a[3] = {},
(* const b)[3] = NULL,
* const c(int, char),
(* const d)(int, char) = NULL;
// ca mai sus, dar in final e vorba de intregi constanti
int const cls1::* a,
cls2::* const b = NULL,
(* cls2::* const * (cls1::* const f)(int, char)) [3] = NULL;
// 'a' este pointer la atribut al lui 'cls1' de tip intreg constant,
// 'b' este pointer constant la atribut al lui 'cls2' de tip intreg
//
constant,
// 'c' este pointer constant la metoda a lui 'cls1' care primeste
//
primeste un 'int' si un 'char' si returneaza un pointer constant
//
la un pointer (neconstant) la atribut al lui 'cls2' care este
//
pointer (normal si neconstant) la vector de 3 intregi constanti
Compilatorul nu accepta operatiile care ar permite incalcarea restrictiei
de modificare a valorii impusa de calificatorul 'const'; de aceea:
- nu se poate asigna adresa unei zone 'const' unui pointer la zona
non 'const' (indiferent daca pointerul este sau nu unul constant);
nu se poate asocia o referinta la zona non 'const' la o locatie 'const';
- unui obiect 'const' nu se poate apela o metoda non 'const' (a se vedea
sectiunea V.3.); intr-adevar, adresa sa (care este de tip 'clasa const *')
s-ar pasa ca parametru actual lui 'this', care este de tip 'clasa *'.
In plus, o clauza a standardului C++ cere ca obiectele temporare,
cum sunt cele returnate prin valoare de functii, sa nu poata fi pasate ca
parametri prin referinta decat daca parametrul formal este declarat ca
referinta la zona 'const' (pentru detalii, a se vedea lectia ***).
Observatie: unele compilatoare (de exemplu gcc/g++) permit ca adresa unei
zone 'const' sa fie asignata unui pointer la zona non 'const', daca se face
un cast explicit; tentativa de modificare a zonei respective prin intermediul
pointerului poate avea insa efecte imprevizibile; exemplu:
int const a = 1;
int *b;
int main(){
b = &a;
// eroare, nu este permisa conversia de la 'int const *' la 'int *'
b = (int *)&a;
// OK
*b = 2;
// eroare la executie: Segmentation fault
}
V.2. Atribute constante

=======================
O clasa poate avea atribute constante; ele sunt read-only si se declara
asemanator atributelor obisnuite, dar cu 'tip const' sau 'const tip'.
Atributele constante sunt initializate la instantierea lor (prin definitia
lor) iar instantele vor pastra valoarea respectiva pe toata durata existentei
lor; in particular:
- Daca atributul constant este static, declaratia sa din clasa sau definitia
sa exterioara clasei (nu ambele) trebuie sa contina initializarea; scrierea
este asemanatoare celei pentru constante care nu sunt atribute (a se vedea
inceputul sectiunii V.1), mai exact (ilustram cazul definitiei exterioare):
* daca atributul constant static este non-obiect, definitia va fi:
<Tip> const <Nume clasa>::<Nume atribut> = <Valoare>;
sau:
<Tip> const <Nume clasa>::<Nume atribut>(<Valoare>);
in declaratia interioara clasei nu se poate folosi varianta cu
paranteze: (<Valoare>);
* daca atributul constant static este obiect, definitia va fi:
<Nume clasa atribut> const <Nume clasa>::<Nume atribut>
= {<Expresie 1>, ..., <Expresie n>};
(daca clasa obiectului atribut este structura agregat)
sau (pentru a selecta un constructor adecvat):
<Nume clasa atribut> const <Nume clasa>::<Nume atribut>
(<Parametri actuali>);
sau echivalent, cand exista un singur parametru actual:
<Nume clasa atribut> const <Nume clasa>::<Nume atribut>
= <Parametru actual>;
in declaratia interioara clasei nu se poate face initializarea;
* initializarea atributului constant static poate lipsi atat din declaratia
interioara clasei cat si din definitia exterioara, in una din urmatoarele
situatii:
** atributul respectiv nu va fi accesat (nici prin 'atribut', din metodele
clasei, nici prin 'obiect.atribut' sau 'clasa::atribut'); atunci
definitia exterioara poate chiar lipsi cu totul;
** atributul este obiect iar clasa sa are un constructor implicit (i.e.
fara parametri sau cu toti parametrii impliciti) definit de utilizator
(cel generat automat de compilator nu este suficient);
atunci vom scrie:
<Nume clasa atribut> const <Nume atribut>;
// in declaratia din clasa
<Nume clasa atribut> const <Nume clasa>::<Nume atribut>;
// in definitia exterioara

- Daca atributul constant este nestatic, el se poate initializa prin


initializatori scrisi in listele de initializatori ale constructorilor
clasei; in principiu (dar nu obligatoriu), in acest caz clasa trebuie
sa contina constructori definiti de utilizator si fiecare dintre ei
trebuie sa constina cate un initializator pentru atributul respectiv;
initializatorul poate fi de forma urmatoare:
* in cazul atributelor constante nestatice non obiect:
<Nume atribut>(<Valoare>)
* in cazul atributelor constante nestatice obiect:
<Nume atribut>(<Parametri actuali>)
* initializarea unui atribut constant nestatic poate lipsi complet sau doar
din listele de initializatori ale unora dintre constructori, in una din
urmatoarele situatii:
** clasa nu va fi instantiata;
** clasa este structura agregat si toate instantierile ei contin
liste de clauze de initializare intre '{}'; listele trebuie sa contina
clauze pentru toate atributele constante nestatice (nici unul nu poate fi
ingorat);
** atributul este obiect iar clasa sa are un constructor implicit definit
de utilizator (cel generat automat de compilator nu este suficient);
atunci acolo unde lipseste initializatorul se foloseste acest
constructor.
Exemplu:
========
#include<iostream>
using namespace std;
class clsobag{ public: int n; }; // clasa agregat
class clsob{
public:
int n;
clsob();
clsob(int);
};
clsob::clsob(): n(11){}
clsob::clsob(int x): n(x){}
class cls1{
public:
static int const i;
static clsob const a;
static void f();
};
clsob const cls1::a;
void cls1::f(){cout << a.n << endl;}
// 'cls1::i', 'cls1::a' nu sunt initializate; 'cls1::i' nici nu e definit;
// acest lucru este posibil deoarece:
// 'cls1::i' nu este accesat in program;
// 'cls1::a' este accesat (in 'cls1::f'), dar are in clasa sa 'cls1' un
//
constructor implicit definit de utilizator (si i se aplica acesta);

class cls2{
public:
static int const i1 = 1;
static int const i2;
static clsob const a1;
static clsob const a2;
static clsobag const a3;
static void f();
};
int const cls2::i1;
int const cls2::i2 = 2;
clsob const cls2::a1;
clsob const cls2::a2(3); // echivalent cu: 'clsob const cls2::a2 = 3;'
clsobag const cls2::a3 = {4};
void cls2::f(){
cout << i1 << " " << i2 << " "
<< a1.n << " " << a2.n << " " << a3.n << endl;
}
// 'cls2::i1' este initializat in declaratie, 'cls2::i2' in definitie;
// 'cls2::a1' este neintializat (se va folosi constructorul implicit definit
// de utilizator 'clsob()');
// 'cls2::a2' este initializat cu constructorul 'clsob(int)'
// 'cls2::a3' este initializat cu lista de clauze intre '{}'
int main(){
cls1::f(); // afisaza: 11
cls2::f(); // afisaza: 1 2 11 3 4
}
Exemplu:
========
#include<iostream>
using namespace std;
class clsobag{ public: int n; }; // clasa agregat
class clsob{
public:
int n;
clsob();
clsob(int);
clsob(int, int);
};
clsob::clsob(): n(11){}
clsob::clsob(int x): n(x){}
clsob::clsob(int x, int y): n(x + y){}
class cls1{
public:
int const i;
clsob const a;
};
// 'cls1::i', 'cls1::a' nu sunt initializate;
// acest lucru este posibil deoarece clasa 'cls1' nu va fi instantiata
// in program;
class cls2{

public:
int const i;
clsobag const a;
};
// 'cls1::i', 'cls1::a' nu sunt initializate;
// acest lucru este posibil deoarece clasa 'cls2' este structura agregat
// si toate instantierile sale vor contine liste de clauze intre '{}',
// care specifica valori pentru toate atributele constante nestatice;
class cls3{
public:
int const i;
clsob const a;
cls3(int);
cls3(int, int);
};
cls3::cls3(int):i(1), a(2, 3) {}
cls3::cls3(int, int):i(4) {}
// 'cls3::a' nu este initializat in toti constructorii lui 'cls3';
// acest lucru este posibil deoarece clasa 'clsob' are un constructor
// implicit definit de utilizator (iar in constructorul 'cls3(int, int)'
// se va folosi acesta)
int main(){
clsobag x; // 'x.n' primeste automat o valoare implicita
// cls2 ob1 = {}, ob2 = {5};
// eroare, raman atribute constante nestatice neinitializate
cls2 ob3 = {6, x};
cout << ob3.i << " " << ob3.a.n << endl; // afisaza: 6 -1218194684
cls3 ob4(7);
cout << ob4.i << " " << ob4.a.n << endl; // afisaza: 1 5
}
V.3. Metode constante
=====================
o METODA CONSTANTA se specifica astfel plasand cuvantul cheie 'const' dupa
paranteza inchisa a listei de parametri formali; acest lucru trebuie facut
atat la declaratia din clasa cat si la definitia exterioara clasei.
O metoda nestatica constanta va avea parametrul 'this' de tip
'clasa const * const' (pointer constant catre zona constanta); prin urmare:
- metoda nu va putea modifica atributele nestatice invocate fara
precizarea obiectului (i.e. de la obiectul curent);
intr-adevar, ele vor fi accesate implicit sub forma 'this->atribut',
deci accesul este read-only;
metoda va putea modifica insa atributele statice si atributele invocate
cu precizarea obiectului;
- metoda nu va putea apela metode nestatice neconstante invocate fara
precizarea obiectului (i.e. de la obiectul curent);
intr-adevar, 'this' trebuie transmits si lor si nu este permisa conversia
de la 'clasa const * const' la 'clasa * const';
metoda va putea apela insa metodele nestatice constante, metodele statice
si metode (constante sau nu) invocate cu precizarea obiectului.
Metodele statice si funtiile independente nu pot fi constante.

Exemplu:
========
#include<iostream>
using namespace std;
class cls{
public:
static int i;
int j;
static void f();
static void f2() const; // eroare, metodele statice nu pot fi constante
void g1();
void g2() const;
void test1(int) const;
void test2(int);
static void test3(int);
};
int cls::i;
void cls::f(){}
void cls::f2() const {} // eroare, metodele statice nu pot fi constante
void cls::g1() {}
void cls::g2() const {}
cls ob;
void cls::test1(int x) const {
i = x;
// OK, 'i' este static
j = x;
// eroare, 'this->j' este read-only
ob.j = x;
// OK; in 'main' se va apela 'ob.test1()', deci practic 'ob.j = x'
// va modifica 'j' din obiectul curent, dar acum se poate deoarece
// prin 'ob.' zona nu mai este privita read-only ca prin 'this->'
cout << j << endl; // OK, nu se incearca modificarea lui 'this->j'
f();
// OK, 'f' este static
g1();
// eroare, 'test' nu poate transmite 'this' al lui, care este de tip
// 'cls const * const', lui 'g1', care necesita 'cls * const'
g2();
// OK, 'g2' este metoda constanta
cls a;
a.j = x; // OK, se modifica 'j' in alt obiect decat cel curent
a.g1(); // OK, se apeleaza 'g1' pentru alt obiect decat cel curent
}
void cls::test2(int x) {
i = x; j = x; cout << j << endl; f(); g1(); g2();
cls a; a.j = x; a.g1();
// intr-o metoda non const putem accesa membrii const si non const
// atat ai obiectului curent cat si a altor obiecte;
// daca dintr-o metoda nestatica non const apelam o metoda nestatica
// const, pasarea lui 'this' presupune conversia de la
// 'cls * const' la 'cls const * const', care este permisa
}
void cls::test3(int x) {
i = x; f();
cls a; a.j = x; a.g1(); a.g2();
// intr-o metoda statica putem accesa membrii statici si membrii
// const si non const ai unor obiecte mentionate explicit;

//

metodele statice nu au 'this';

}
void h() const {} // eroare, functiile independente nu pot fi const
int main(){ob.test1(10); ob.test2(20); ob.test3(30);}
Obiectelor constante nu li se pot apela metodele nestatice neconstante (ci
doar cele nestatice constante si cele statice).
Inr-adevar, aceste metode au 'this' de tipul 'clasa * const' iar adresa
obiectului constant este de tip 'clasa const * const'.
Exemplu:
========
#include<iostream>
using namespace std;
class cls{
public:
static int i;
int j;
static void f();
void g1();
void g2() const;
};
int cls::i;
void cls::f(){}
void cls::g1() {}
void cls::g2() const {}
cls const ob = {10}; // se initializeza doar 'j' (care este nestatic)
int main(){
ob.i = 1; cout << ob.i << endl;
// OK, 'i' este independent de obiectele lui 'cls' si nu este 'const';
// afisaza: 1
ob.j = 2; // eroare, 'ob.j' este read-only (deoarece 'ob' este 'const')
cout << ob.j << endl; // Ok, afisaza: 10
ob.f(); // OK, 'f' este static
ob.g1(); // eroare, 'ob' este 'const' iar 'g1' este nestatica neconstanta
ob.g2(); // OK
}
V.4. Atribute mutabile
======================
Atributele mutabile se specifica astfel la declarare insotind tipul lor de
baza de cuvantul cheie 'mutable'.
Un atribut mutabil poate fi modificat chiar daca este parte a unui obiect
read-only (de exemplu declarat ca o constanta sau accesat printr-un pointer
la zona constanta); in particular, metodele constante pot modifica atributele
mutabile invocate fara precizarea obiectului (i.e. accesate la obiectul
curent prin 'this').
Doar atributele nestatice pot fi mutabile.
Exemplu:

========
class cls{
public:
int i;
mutable int j;
void g1(int);
void g2(int) const;
};
void cls::g1(int x) {
i = x; j = x; // OK
}
void cls::g2(int x) const {
i = x; // eroare, 'this->i' este read-only
j = x; // OK, atribut mutabil
}
int main(){
cls const ob = {1, 2};
ob.i = 3; // eroare, 'ob.i' este read-only
ob.j = 4; // OK, atribut mutabil
}
VI. Membri volatile
===================
TODO
VII. Calificatori cv
====================
TODO - a se vedea si:
http://en.cppreference.com/w/cpp/language/cv
http://en.wikipedia.org/wiki/Immutable_object
Lectia 5: Atribuire, Constructori si Destructori
================================================
I. Atribuire
============
In C++ putem atribui valori obiectelor (in particular structuri, uniuni)
folosind OPERATORUL DE ATRIBUIRE (ASSIGNMENT OPERATOR) '='; ca majoritatea
celorlalti operatori, el poate fi supraincarcat.
Un caz special al operatorului de atribuire este OPERATORUL DE ATRIBUIRE PRIN
COPIERE (COPY ASIGNMENT OPERATOR), numit deseori doar "operator de atribuire",
unde sursa (membrul drept) si destinatia (membrul stang) sunt obiecte de aceeasi
clasa.
tip x;
class cls{
...
} a, b;
...

a = x; // operator de atribuire
a = b; // operator de atribuire prin copiere
Pentru orice clasa definita de utilizator, daca programatorul nu a declarat
explicit un operator de atribuire prin copiere, compilatorul genereaza automat
unul, numit OPERATOR DE ATRIBUIRE PRIN COPIERE IMPLICIT (DEFAULT COPY
ASSIGNMENT OPERATOR), a.i. pentru orice doua obiecte ale clasei 'a', 'b' va
avea sens automat expresia 'a = b'.
Daca clasa este 'cls', operatorul de atribuire prin copiere implicit este o
metoda nestatica inline publica cu signatura:
cls& cls::operator= (cls const &);
care copiaza membru cu membru (memberwise) atributele nestatice ale
obiectului sursa (dat ca parametru) in cele ale obiectului destinatie
(obiectul curent). Pentru atributele nestatice non obiect se foloseste
copierea bit cu bit; pentru cele obiect, se foloseste operatorul lor propriu
de atribuire prin copiere, care poate fi cel implicit sau unul definit de
utilizator; de asemenea, daca 'cls' mosteneste alte clase, operatorul de
atribuire prin copiere implicit apeleaza operatorii de atribuire prin copiere
ai claselor de baza (care pot fi cei impliciti sau unii definiti de
utilizator), pentru a copia partea mostenita. Operatorii de atribuire prin
copiere ai claselor de baza se apeleaza in ordinea declararii acestor clase
in lista claselor mostenite si inaintea operatorilor de atribuire prin
copiere ai atributelor nestatice obiect; operatorii de atribuire prin copiere
ai atributelor nestatice obiect se apeleaza in ordinea declararii acestor
atribute in clasa. In final, operatorul de atribuire prin copiere implicit
returneaza prin referinta obiectul curent.
Daca toata informatia proprie unui obiect se afla in locatia sa, operatorul
de atribuire prin copiere implicit este satisfacator (duplica informatia in
concordanta cu sensul intuitiv al notiunii de copiere). Daca o parte din
aceasta informatie se afla in alta parte (de exemplu in niste zone alocate
dinamic si doar pointate din locatia obiectului, sau in niste fisiere iar in
locatia obiectului se retin descriptorii acestora), operatorul implicit nu
mai este satisfacator intotdeauna si trebuie folosit unul definit de
utilizator (obtinut prin supraincarcarea explicita a lui '='), care sa faca
o 'copiere in profunzime' (deep copy).
Operatorul '=' se poate supraincarca doar ca metoda nestatica a unei clase
(pentru detalii privind supraincarcarea operatorilor, a se vedea lectia ***).
Daca intr-o clasa am scris un operator '=' prin copiere (i.e. care are ca
parametru un obiect de aceeasi clasa, nu conteaza daca este prin
valoare/referinta, cu/fara 'const', etc.) definit de utilizator el ia
locul celui implicit (compilatorul nu-l mai genereaza pe acela). Operatorul
'=' prin copiere definit de utilizator nu mai apeleaza insa automat
operatorii '=' prin copiere ai claselor de baza si ai atributelor obiect, ci
doar daca o cerem noi explicit prin codul sau.
Daca in clasa am scris operatori '=' care nu sunt de copiere, ei vor
coexista cu cel implicit (compilatorul il genereaza si pe acela). Fiecare
operator '=' va fi selectat ulterior conform regulilor generale de la
supraincarcarea functiilor si operatorilor.
Exemplu:
========
#include<iostream>
using namespace std;
class complex{

double re, im;


public:
void setre(double); void setim(double);
double getre(); double getim();
};
void complex::setre(double x) {re = x;}
void complex::setim(double x) {im = x;}
double complex::getre() {return re;}
double complex::getim() {return im;}
int main(){
complex a, b;
a.setre(1); a.setim(2);
b = a;
cout << b.getre() << " " << b.getim()
// afisaza: 1 2
b.setre(10); b.setim(20);
cout << a.getre() << " " << a.getim()
<< b.getre() << " " << b.getim()
// afisaza: 1 2 10 20
a + b; // eroare, '+' nu este definit
}

<< endl;
<< " "
<< endl;
intre complex si complex

Comentarii:
- a fost suficient doar sa definim clasa 'complex' si automat capata
sens expresia 'a = b' intre doua obiecte ale sale; intr-adevar,
compilatorul a adaugat automat clasei o metoda operator '=' (operatorul
de atribuire prin copiere implicit):
complex& complex::operator=(complex const &);
acest lucru nu se intampla si cu alti operatori, de ex. '+'; de aceea,
daca vrem sa aiba sens si expresia 'a + b' trebuie sa adaugam explicit
un '+' definit de utilizator;
- in cazul clasei 'complex' operatorul '=' de copiere implicit este
satisfacator, deoarece toata informatia proprie unui obiect se afla in
membrii sai 're' si 'im', care sunt in locatia obiectului;
intr-adevar, afisarile au aratat ca prin 'b = a' se duplica informatiile
lui 'a', iar 'b' are o copie proprie a acestor informatii, care daca se
modifica nu sunt afectate informatiile lui 'a'.
Exemplu:
========
#include<iostream>
#include<string.h>
using namespace std;
class string1{
char s[20];
public:
int set(char const *);
char const *get();
char& operator[] (int);
};
int string1::set(char const *t){
if(strlen(t) > 19) return 0;
strcpy(s, t);
return 1;
}

char const * string1::get() {return s;}


char& string1::operator[] (int i) {
if(0 <= i && i < strlen(s)) return s[i];
return s[0];
}
class string2{
char *s;
public:
string2();
// ~string2();
int set(char const *);
char const *get();
char& operator[] (int);
};
string2::string2() {s = NULL;}
// string2::~string2() {delete []s;}
int string2::set(char const *t){
char *aux = new char[strlen(t) + 1];
if(aux == NULL) return 0;
delete s; s = aux; strcpy(s, t); return 1;
}
char const * string2::get() {return s;}
char& string2::operator[] (int i) {return s[i];}
class string3{
char *s;
public:
string3();
~string3();
string3& operator=(string3 const &);
int set(char const *);
char const *get();
char& operator[] (int);
};
string3::string3() {s = NULL;}
string3::~string3() {delete []s;}
string3& string3::operator=(string3 const & t){
set(t.s); return *this;
}
int string3::set(char const *t){
char *aux = new char[strlen(t) + 1];
if(aux == NULL) return 0;
delete s; s = aux; strcpy(s, t); return 1;
}
char const * string3::get() {return s;}
char& string3::operator[] (int i) {return s[i];}
int main(){
string1 a1, b1;
a1.set("abc"); b1 = a1; b1[1] = 'x'; cout<<a1.get()<<" "<<b1.get()<<endl;
// afisaza: abc axc
string2 a2, b2;
a2.set("abc"); b2 = a2; b2[1] = 'x'; cout<<a2.get()<<" "<<b2.get()<<endl;
// afisaza: axc axc
string3 a3, b3;

a3.set("abc"); b3 = a3; b3[1] = 'x'; cout<<a3.get()<<" "<<b3.get()<<endl;


// afisaza: abc axc
}
Comentarii:
- Obiectele clasei 'string1' au toata informatia proprie in locatia lor,
deci operatorul '=' de copiere implicit pentru 'string1' este satisfacator;
intr-adevar, copiind pe 'a1' in 'b1' si modificand apoi pe 'b1' nu s-a
modificat 'a1' (s-au duplicat toate informatiile proprii).
Obiectele claselor 'string2' si 'string3' nu au toata informatia proprie
in locatia lor; intr-adevar, caracterele propriuzise ale stringului sunt
intr-o zona alocata dinamic iar in locatia obiectului se retine doar
adresa acestei zone, in atributul 's'.
- Pentru clasa 'string2' nu am definit explicit un operator '=' de copiere,
deci compilatorul a generat automat pe cel implicit, iar acesta nu este
satisfacator, deoarece nu duplica zona dinamica ce contine caracterele
stringului ci doar adresa acesteia (retinuta in atributul 's'); astfel,
dupa 'b2 = a2' ambele obiecte 'a2' si 'b2' pointeaza aceeasi zona, asa
ca modificand pe 'b2' s-a modificat 'a2' (pentru ambele s-a afisat 'axc');
- Pentru clasa 'string3' am remediat aceasta deficienta adaugand un operator
'=' de copiere definit de utilizator, care duplica si zona dinamica
(folosind metoda 'set'); el a luat locul celui implicit (acela nu mai este
generat de compilator);
- In cazul claselor 'string2' si 'string3' setarea unui nou sir presupune
dezalocarea zonei dinamice vechi si alocarea alteia noi; pentru a avea
sens primul 'delete' efectuat de un obiect, a fost nevoie de un
constructor care sa faca 's = NULL'; pentru a elibera zona dinamica la
sfarsitul existentei unui obiect, a fost nevoie de un destructor care sa
faca 'delete s'.
In cazul clasei 'string2' insa, unde operatorul de atribuire prin copiere
folosit este cel implicit iar acesta duplica doar adresa zonei dinamice
nu si zona respectiva, a.i. obiectele 'a2' si 'b2' ajung sa pointeze
aceeasi zona, prezenta destructorului conduce la efecte imprevizibile,
deoarece in final zona dinamica se dezaloca de doua ori - de aceea
destructorul clasei 'string2', desi necesar in principiu, a fost eliminat
(comentat).
Daca o clasa are atribute nestatice constante, operatorul '=' prin copiere
implicit nu va functiona (daca nu scriem in clasa un operator '=' prin
copiere definit de utilizator si incercam sa atribuim obiecte folosind
expresii de tip 'a = b', se va semnala eroare la compilare). Pentru a putea
efectua atribuiri intre obiectele unei asemenea clase, trebuie sa folosim
un operator '=' definit de utilizator.
Exemplu:
========
#include<iostream>
using namespace std;
class cls1{
public:
int const i;
int j;
cls1(int, int);
};
cls1::cls1(int x, int y):i(x) {j = y;}
// 'cls1' are operator '=' de copiere implicit
class cls2{

public:
int const i;
int j;
cls2(int, int);
cls2& operator=(cls2 const &);
};
cls2::cls2(int x, int y):i(x) {j = y;}
cls2& cls2::operator=(cls2 const &p) {
// i = p.i;
j = p.j;
cout << "cls2, atribuire utilizator\n";
return *this;
}
// 'cls2' are operator '=' de copiere definit de utilizator
int main(){
cls1 a(1, 10), b(2, 20); cls2 c(3, 30), d(4, 40);
a = b;
// eroare, nu se poate folosi operatorul de atribuire implicit
// in cazul atributului nonstatic constant 'i'
c = d;
cout << c.i << " " << c.j << endl;
// afisaza:
//
cls2, atribuire utilizator
//
3 40
}
Comentariu: in operatorul '=' definit de utilizator n-am fi putut adauga si
o instructiune 'i = p.i', deoarece membrul 'i' este read-only si s-ar fi
semnalat eroare la compilare; deci, acest operator copiaza doar pe 'j'.
Un operator '=' definit de utilizator poate fi declarat cu orice tip al
parametrului formal si al valorii returnate, cu conditia sa respectam
regulile generale de la supraincarcarea operatorilor (a se vedea lectia
***).
Daca vrem sa fie operator '=' prin copiere, trebuie ca parametrul formal
sa fie obiect al aceleiasi clase (nu conteza daca este prin valoare sau
referinta, daca este 'const' sau nu); nu conteaza tipul valorii returnate.
Prezenta unui asemenea operator va determina compilatorul sa nu-l mai
genereze pe cel implicit pentru clasa respectiva.
De obicei, pentru o clasa 'cls', operatorul '=' prin copiere definit de
utilizator se declara cu o signatura similara celui implicit:
cls& cls::operator= (cls const &parametru) { ... return *this;}
Faptul ca parametrul formal este prin referinta face ca intrarea in apel
sa fie mai rapida (daca ar fi fost prin valoare, s-ar fi creat un obiect
nou, pentru care s-ar fi apelat constructorul de copiere); faptul ca
referinta este la zona constanta ne permite sa pasam ca parametri actuali si
obiecte temporare (pentru detalii suplimentare, a se vedea capitolele ***
din aceasta lectie). Faptul ca returneaza referinta la obiectul curent ne
permite sa scriem atribuiri inlantuite: 'a = b = c'. Toate aceste avantaje
sunt prezente si la operatorul implicit.
Reamintim insa ca un operator '=' definit de utilizator nu va apela
operatorii '=' ai claselor de baza si ai atributelor obiect automat ci doar
daca vom cere noi aceasta explicit prin codul sau.
Exemplu:
========

#include<iostream>
using namespace std;
class cls1{
public:
int i;
cls1(int);
};
cls1::cls1(int x):i(x) {}
// 'cls1' are operator '=' prin copiere implicit
cls1 h1(){cls1 ob(10); return ob;}
// functie ce returneaza prin valoare un obiect de clasa 'cls1';
// acesta va fi creat ca un obiect temporar
class cls2{
public:
int i;
cls2(int);
void operator=(cls2 &);
};
cls2::cls2(int x):i(x) {}
void cls2::operator=(cls2 &p) {
i = p.i;
cout << "cls2, atribuire utilizator\n";
}
// 'cls2' are operator '=' prin copiere definit de utilizator
cls2 h2(){cls2 ob(20); return ob;}
// functie ce returneaza prin valoare un obiect de clasa 'cls2';
// acesta va fi creat ca un obiect temporar
int main(){
cls1 a(1), b(2), c(3); cls2 d(4), e(5), f(6);
a = b; a = b = c; a = h1(); // OK
d = e; // afisaza: cls2, atribuire utilizator
d = e = f;
// eroare, nu se poate atribui valoarea returnata de 'e = f',
// adica 'void', lui 'd'
d = h2();
// eroare, nu se poate pasa obiectul temporar returnat de 'h2' ca
// parametru prin referinta la zona neconstanta lui '='
}
Comentarii:
- Operatorul '=' definit de utilizator pentru 'cls2' este unul de copiere
(deoarece parametrul sau este obiect al aceleiasi clase), chiar daca
nu are aceeasi singatura ca cel implicit - parametrul este referinta la
zona neconstanta si returneaza 'void'; astfel, pentru 'cls2' compilatorul
nu a mai generat operatorul implicit (la 'd = e' s-a afisat:
'cls2, atribuire utilizator').
- Operatorul '=' de copiere implicit pentru 'cls1' a putut fi inlantuit,
deoarece returneaza referinta la obiectul curent; cel definit de utilizator
pentru 'cls2' nu, deoarece returneaza 'void'; ca sa mearga, in loc de
'd = e = f;' am fi putut scrie: 'e = f; d = e;'.
- Lui 'a' i-am putut atribui obiectul temporar returnat prin valoare de 'h1',
deoarece operatorul '=' de copiere implicit al lui 'cls1' are parametrul
referinta la zona constanta; cel definit de utilizator pentru 'cls2' are
parametrul referinta la zona neconstanta si astfel nu a fost acceptat
'd = h2();'; ar fi mers insa daca parametrul ar fi fost prin valoare:

'cls2 p' (si atunci 'p' s-ar fi creat ca un obiect nou, existent pe
perioada apelului si initializat din parametrul actual folosind
constructorul de copiere).
Exemplul anterior ne arata ca in cazul operatorului '=' implicit nu se
aplica regulile generale de supraincarcare a functiilor si operatorilor,
in sensul ca daca in clasa adaugam un operator '=' de copiere definit de
utilizator (i.e. avand ca parametru un obiect al aceleiasi clase), el nu
mai coexista cu cel implicit, chiar daca tipul parametrului difera; pur si
simplu, pe cel implicit compilatorul nu il mai genereaza.
Daca in clasa adaugam mai multi operatori '=' de copiere definiti de
utilizator dar cu tip al parametrului diferit (conform regulilor de
supraincarcare a functiilor si operatorilor), ei vor coexista intre ei si
impreuna cu alti eventuali operatori '=' (care nu sunt de copiere).
Exemplu:
========
#include<iostream>
using namespace std;
class cls1{
public:
int i;
cls1(int);
cls1& operator= (int const &);
};
cls1::cls1(int x):i(x) {}
cls1& cls1::operator= (int const &x) {
i = x;
cout << "cls1& cls1::operator= (int const &)\n";
return *this;
}
// 'cls1' are in continuare operator '=' prin copiere implicit,
//
deoarece operatorul '=' definit de utilizator nu este de
//
copiere (parametrul sau nu este de tip 'cls1')
class cls2{
public:
int i;
cls2(int);
cls2& operator= (cls2 const &);
cls2& operator= (cls2 &);
};
cls2::cls2(int x):i(x) {}
cls2& cls2::operator=(cls2 const &p){
i = p.i;
cout << "cls2& cls2::operator= (cls2 const &)\n";
return *this;
}
cls2& cls2::operator=(cls2 &p){
i = p.i;
cout << "cls2& cls2::operator= (cls2 &)\n";
return *this;
}
// 'cls2' are doi operatori '=' prin copiere definiti de utilizator
// si nu il mai are pe cel implicit
cls2 h2(){cls2 ob(20); return ob;}

// functie ce returneaza prin valoare un obiect de clasa 'cls2';


// acesta va fi creat ca un obiect temporar
int main(){
cls1 a(1), b(2); cls2 d(4), e(5);
a = b;
// nu se afisaza nimic, deci s-a folosit '='-ul implicit
a = 100; // afisaza: cls1& cls1::operator= (int const &)
d = e;
// afisaza: cls2& cls2::operator= (cls2 &)
d = h2(); // afisaza: cls2& cls2::operator= (cls2 const &)
}
Comentariu: compilatorul a ales ce operatori '=' sa aplice in cazul lui
'd' dupa regula generala de la supraincarcarea functiilor si operatorilor,
bazata pe potrivirea intre tipurile parametrilor actuali si formali;
- 'e' este de tip 'cls2', deci a fost ales operatorul cu parametru 'cls2&'
(la primul pas al regulii, potrivire exacta);
- 'h(2)' returneaza un obiect temporar, care nu se poate pasa daca
parametrul este referinta la zona neconstanta, asa ca a fost ales
operatorul cu parametru 'cls2 const &'.
Daca mai adaugam si un operator 'cls2& cls2::operator=(cls2 p)' (deci cu
parametru prin valoare) la ambele atribuiri 'd = e;' si 'd = h2();' s-ar
fi semnalat eroare de ambiguitate.
Exemplu:
========
#include<iostream>
using namespace std;
class clsbaz1{public: int i;};
// 'clsbaz1' are operator '=' de copiere implicit
class clsbaz2{public: int i; void operator=(clsbaz2 const &);};
void clsbaz2::operator=(clsbaz2 const &p) {cout << "= din clsbaz2\n";}
// 'clsbaz2' are operator '=' de copiere definit de utilizator
class clsbaz3{public: int i; void operator=(clsbaz3 const &);};
void clsbaz3::operator=(clsbaz3 const &p) {cout << "= din clsbaz3\n";}
// 'clsbaz3' are operator '=' de copiere definit de utilizator
class clsob1{public: int i;};
// 'clsob1' are operator '=' de copiere implicit
class clsob2{public: int i; void operator=(clsob2 const &);};
void clsob2::operator=(clsob2 const &p) {cout << "= din clsob2\n";}
// 'clsob2' are operator '=' de copiere definit de utilizator
class clsob3{public: int i; void operator=(clsob3 const &);};
void clsob3::operator=(clsob3 const &p) {cout << "= din clsob3\n";}
// 'clsob3' are operator '=' de copiere definit de utilizator
class clsob4{public: int i; void operator=(clsob4 const &);};
void clsob4::operator=(clsob4 const &p) {cout << "= din clsob4\n";}
// 'clsob4' are operator '=' de copiere definit de utilizator
class cls1: public clsbaz3, public clsbaz2, public clsbaz1 {
public:
static clsob4 s;

int i; clsob3 j; clsob2 k; clsob1 l;


};
clsob4 cls1::s;
// 'cls1' are operator '=' de copiere implicit
class cls2: public clsbaz3, public clsbaz2, public clsbaz1 {
public:
int i; clsob3 j; clsob2 k; clsob1 l;
void operator=(cls2 const &);
};
void cls2::operator=(cls2 const &p) {cout << "= din cls2\n";}
// 'cls2' are operator '=' de copiere definit de utilizator
int main(){
cls1 a, b; cls2 c, d;
a = b;
// afisaza:
// = din clsbaz3
// = din clsbaz2
// = din clsob3
// = din clsob2
cout << "---\n";
// afisaza:
// --c = d;
// afisaza:
// = din cls2
}
Comentarii:
- Operatorul '=' de copiere implicit (din 'cls1') a apelat automat
operatorii '=' de copiere ai claselor da baza, in ordinea delcalrarii lor
la mostenire (intai 'clsbaz2', apoi 'clsbaz1'), apoi operatorii '=' de
copiere ai atributelor nestatice obiect, in ordinea declararii lor in clasa
(intai 'j' apoi 'k'), chiar daca acesti '=' sunt definiti de utilizator
('clsbaz1' si 'clsob1' au operatorii '=' de copiere impliciti, care nu
afisaza mesaje si de aceea, desi s-au apelat, nu s-a afisat nimic in acest
sens). Pentru membrul static 's' nu s-a apelat operatorul '=' de copiere
(nu s-a afisat si '= din clsob4').
- Operatorul '=' de copiere definit de utilizator pentru clasa 'cls2' nu mai
apeleaza automat operatorii '=' de copiere ai claselor de baza si ai
atributelor nestatice obiect si de aceea in cazul lui nu s-a afisat decat
'= din cls2'.
Daca dorim ca si el sa apeleze operatorii respctivi, putem sa cerem asta
explicit in corpul lui scriind:
void cls2::operator=(cls2 const &p) {
clsbaz3::operator=(p); clsbaz2::operator=(p); clsbaz1::operator=(p);
i = p.i; j = p.j; k = p.k; l = p.l;
cout << "= din cls2\n";
}
si atunci va afisa:
=
=
=
=
=

din
din
din
din
din

clsbaz3
clsbaz2
clsob3
clsob2
cls2

notam ca 'p' poate fi pasat ca parametru actual celor trei '=' deoarece
o referinta la o clasa de baza poate fi initializata cu un obiect al
clasei derivate (dar va accesa in el doar membri mosteniti), a se vedea
lectia ***.
Exemplu:
========
#include<iostream>
using namespace std;
class clsbaz1{public: void operator=(clsbaz1); void operator=(clsbaz1 &);};
void clsbaz1::operator=(clsbaz1 p)
{cout << "void clsbaz1::operator=(clsbaz1)\n";}
void clsbaz1::operator=(clsbaz1 &p)
{cout << "void clsbaz1::operator=(clsbaz1 &)\n";}
// 'clsbaz1' are doi operatori '=' de copiere definiti de utilizator
class clsbaz2{void operator=(clsbaz2 const &);};
void clsbaz2::operator=(clsbaz2 const &p)
{cout << "void clsbaz2::operator=(clsbaz2 const &)\n";}
// 'clsbaz2' are un operator '=' de copiere definit de utilizator,
// dar este privat
class clsob {public: void operator=(clsob const &);};
void clsob::operator=(clsob const &p)
{cout << "void clsob::operator=(clsob const &)\n";}
// 'clsob' are un operator '=' de copiere definit de utilizator
class cls1: public clsbaz1 {};
class cls2: public clsbaz2 {};
class cls3 {clsob n;};
// 'cls1, 'cls2' si 'cls3' au operatori '=' de copiere impliciti
int main(){
clsbaz1 x, y; cls1 a, b;
x = y;
// eroare de ambiguitate intre
// void clsbaz1::operator=(clsbaz1)
// void clsbaz1::operator=(clsbaz1&)
a = b;
// afisaza: void clsbaz1::operator=(clsbaz1)
cls2 c, d;
c = d;
// eroare, 'void clsbaz2::operator=(const clsbaz2&)' este privata
cls3 e, f;
e = f;
// afisaza: void clsob::operator=(clsob const &)
}
Comentarii:
- La 'x = y' s-a semnalat eroare de ambiguitate deoarece ambii operatori
'=' de copiere ai clasei 'clsbaz1' au acelasi grad de compatibilitate
cu tipul parametrului actual 'y';
- La 'a = b', operatorul '=' de copiere implicit al clasei 'cls1',
trebuind sa apeleze un operator '=' de copiere al clasei de baza,
a avut de ales intre aceiasi operatori '=' ai clasei 'clsbaz1' ca in
cazul 'x = y', dar acum nu s-a mai semnalat eroare de ambiguitate.
- La 'c = d', operatorul '=' de copiere implicit al clasei 'cls2'
trebuie sa apeleze un operator '=' de copiere al clasei de baza

'clsbaz2'; singurul asemenea operator este cel definit de utilizator,


care insa este privat in 'clsbaz2', deci prin mostenire este inaccesibil
direct metodelor nou adaugate clasei 'cls2', in particular operatorului
'=' de aici. Daca am fi declarat clasa 'cls2' ca friend a clasei 'clsbaz2',
atunci ar fi mers (operatorul '=' de copiere implicit al lui 'cls2' ar fi
apelat operatorul '=' de copiere definit de utilizator al lui 'clsbaz2' si
s-ar fi afisat: 'void clsbaz2::operator=(clsbaz2 const &)').
- La 'e = f', desi membrul 'n' este privat, operatorul '=' de copiere
este declarat public in clasa lui, 'clsob', deci poate fi apelat in
operatorul '=' de copiere al lui 'cls3' (care nu este metoda sau friend
al lui 'clsob').
Notam ca un operator '=' de copiere se apeleaza in contextul unde a fost
scrisa atribuirea 'obiect1 = obiect2', deci trebuie sa fie accesibil in
acest context. Din acest motiv, pentru a fi cat mai accesibil, se recomanda
a fi declarat public.
II. Clase de alocare ('storage classes')
========================================
TODO - a se vedea si:
http://www.tutorialspoint.com/cplusplus/cpp_storage_classes.htm
http://en.wikipedia.org/wiki/C_syntax#Storage_duration_specifiers
http://en.cppreference.com/w/cpp/language/storage_duration
http://en.wikipedia.org/wiki/Automatic_variable
http://en.wikipedia.org/wiki/Static_variable
http://en.wikipedia.org/wiki/External_variable
http://www.daniweb.com/software-development/cpp/threads/131659/
how-exactly-to-use-extern
http://en.wikipedia.org/wiki/
Dynamic_memory_allocation#Dynamic_memory_allocation
III. Constructori si destructori
================================
Un CONSTRUCTOR, respectiv DESTRUCTOR, este o metoda nestatica care se
apeleaza automat atunci cand obiectul sau curent isi incepe, respectiv
incheie, existenta.
Vom vedea ca o clasa (sau un obiect) poate avea mai multi constructori
si un singur destructor. In acele locuri din cod unde se creaza un obiect
al clasei compilatorul insereaza automat apelul unuia dintre constructori
pentru acel obiect, iar in acele locuri din cod unde se distruge un obiect
al clasei compilatorul insereaza automat apelul destructorului pentru acel
obiect.
Locul unde un obiect este creat sau distrus depinde de domeniul de
vizibilitate si clasa sa de alocare, in felul urmator:
- In cazul obiectelor statice:
* Obiectele statice sunt initializate in doua faze: INITIALIZAREA STATICA
si INITIALIZAREA DINAMICA; initializarea statica are loc prima si
efectueaza initializarea cu 0 sau cu expresii constante; initializarea
dinamica are loc dupa ce au fost efectuate toate initializarile statice
si acum sunt apelati constructorii.
* In ceea ce priveste obiectele statice globale, de obicei compilatoarele
sunt scrise a.i. ele sunt create la inceputul executarii programului,

inainte de 'main' (si atunci i se apeleaza un constructor) si sunt


distruse la sfarsitul executarii programului, dupa 'main' (si atunci i se
apeleaza destructorul); obiectele globale definite intr-un acelasi fisier
al programului sunt create (si li se aplica un constructor) in ordinea
definirii lor (de sus in jos in cadrul fisierului, de la stanga la
dreapta in cadrul unei aceleiasi definitii); pentru obiecte globale
definite in fisiere diferite nu exista reguli privind ordinea crearii.
Nota: standardul C++ afirma ca este la latitudinea celui care elaboreaza
compilatorul (este 'implementation-defined') daca initializarea dinamica
sa se faca inainte de prima instructiune a lui 'main' si cere doar ca in
cazul in care initializarea este amanata dupa prima instructiunea a lui
'main' ea sa se faca inainte de prima folosire a oricarei functii sau
obiect definite in acelasi fisier al programului (translation unit) cu
obiectul initializat.
Astfel, se recomanda ca sa nu se scrie cod care sa depinda de momentul
initializarii obiectelor globale.
* In ceea ce priveste obiectele statice locale (i.e. definite intr-un bloc,
care poate fi corp de functie, instructiune compusa, etc., dar cu
'static'), ele sunt create (si li se aplica un constructor) la prima
executare a blocului in care au fost definite, in momentul cand se
intalneste definitia lor (daca sunt definite mai multe in aceeasi
definitie, ele sunt create in ordine de la stanga la dreapta); ele sunt
distruse (si li se aplica destructorul) la sfarsitul executiei
programului.
* Obiectele statice (indiferent daca sunt globale sau locale) sunt distruse
(si li se aplica destructorul) la sfarsitul executiei programului, in
ordinea inversa crearii lor.
- In cazul obiectelor automatice (i.e. instante ale parametrilor prin
valoare sau variabile definite intr-un bloc, care poate fi corp de functie,
instructiune compusa, etc., dar fara 'static'):
* Ele sunt create si distruse la fiecare executare a functiei/blocului in
care au fost definite.
* Instantele parametrilor prin valoare sunt create (si li se aplica un
constructor) la inceputul apelului functiei, in ordinea declararii lor
de la ultimul spre primul; ele sunt create inainte de a se executa corpul
functiei (si deci inainte de a se crea obiectele automatice definite in
el).
* Variabilele automatice declarate intr-un bloc sunt create (si li se aplica
un constructor) in momentul cand se intalneste definitia lor (daca sunt
definite mai multe in aceeasi definitie, ele sunt create in ordine de la
stanga la dreapta).
* Obiectele automatice create la o executare a unei functii/bloc (indiferent
daca sunt instante ale parametrilor prin valoare sau variabile locale
automatice) sunt distruse (si li se aplica destructorul) la sfarsitul acelei
executii a functiei/blocului respectiv, in ordinea inversa crearii lor.
- In cazul obiectelor dinamice (i.e. create/distruse la cerere):
* Ele sunt create (si li se aplica un constructor) la cerere cu 'new' si
sunt distruse (si li se aplica destructorul) la cerere cu 'delete'.
Functiile 'malloc'/'free' pot fi folosite pentru a aloca/elibera

memorie pentru obiecte, dar acestea nu apeleaza automat constructori/


destructori; deci, ele sunt utile doar pentru a crea/distruge dinamic
date care nu sunt obiecte.
Pentru alte detalii, a se vedea mai jos.
* Un obiect dinamic nedistrus explicit cu 'delete' nu este distrus automat,
nici macar la sfarsitul programului (nu i se apeleaza destructorul);
similar, o zona alocata dinamic cu 'malloc'/'calloc'/'realloc' nu se
dezaloca decat explicit, cu 'free' sau 'realloc(adresa_zonei, 0)'.
Pentru a semnala compilatorului ca o metoda scrisa de utilizator intr-o
clasa este constructor, respectiv destructor, aceasta trebuie sa aiba acelasi
nume cu clasa, respectiv sa aiba ca nume '~' urmat de numele clasei (mai sunt
> si alte restrictii, a se vedea mai jos); de exemplu, daca clasa este 'cls'
atunci un constructor al sau se va numi tot 'cls', iar destructorul sau se va
numi '~cls'.
Atunci, compilatorul va insera automat apeluri ale acestor metode in
locurile din cod unde se face crearea/distrugerea unui obiect al clasei,
pentru a opera asupra obiectului respectiv.
De regula, in constructori punem cod care aloca resurse si fac initializari
ale obiectului nou creat, iar in destructori cod care elibereaza resursele
ocupate de obiectul respectiv inainte de a fi distrus.
Constructorii/destructorii trebuie sa respecte urmatoarele reguli:
- Constructorii:
* Au acelasi nume cu clasa; de exemplu, daca clasa este 'cls', un
constructor al sau se va numi tot 'cls'.
* Nu au un tip returnat (pot contine insa instructiunea 'return;',
fara precizarea unei valori).
* Nu sunt restrictii speciale privind parametrii.
* Optional, au o lista de initializare (a se vedea mai jos).
* Nu li se poate lua adresa, deci nu putem avea pointeri sau referinte
la ei.
* Nu se pot apela cu 'obiect.clasa(parametri)' (ei nu sunt metode
autentice, deoarece obiectul lor curent nu este in intregime creat
pe perioada apelului lor), dar se pot apela cu 'clasa(parametri)'
conducand la crearea unor obiecte temporare (a se vedea capitolul
*** din aceatsa lectie).
* Nu pot fi declarati cu 'static', 'const', 'volatile' si nu pot fi
virtuali.
- Destructorii:
* Au ca nume '~' urmat de numele clasei; de exemplu, daca clasa este
'cls', destructorul sau se va numi '~cls'.
* Nu au un tip returnat (pot contine insa instructiunea 'return;',
fara precizarea unei valori).
* Au lista vida de parametri.
* Nu li se poate lua adresa, deci nu putem avea pointeri sau referinte

la ei.
* Nu pot fi declarati cu 'static', 'const', 'volatile' (dar pot fi
virtuali).
Faptul ca constructorii nu au restrictii in ceea ce priveste parametrii
permite ca intr-o clasa sa avem mai multi constructori, cu respectarea
regulilor generale de supraincarcare a functiilor - ei au toti acelasi
nume cu clasa, deci trebuie sa aiba sirul tipurilor parametrilor formali
diferit. Faptul ca destructorii au restrictia de a avea lista vida de
parametri formali face ca intr-o clasa sa nu putem avea decat un destructor.
Daca intr-o clasa scriem mai multi constructori, se pune problema care
dintre acestia va fi apelat la crearea diverselor obiecte (pe care il va
selecta compilatorul pentru a insera un apel al sau in acel loc din cod).
Crearea unui obiect este intotdeauna descrisa in cod printr-o sintagma
(definitie de variabila, apel de functie, cerere de alocare dinamica cu
'new', etc.) ce permite specificarea unor parametri actuali, iar compilatorul
va selecta de fiecare data constructorul adecvat in functie de
compatibilitatea ca numar si tip intre parametrii actuali si cei formali,
dupa aceleasi reguli si etape ca la supraincarcarea functiilor in general:
potrivire exacta, potrivire pe baza unei conversii predefinite fara pierdere
de informatii, potrivire pe baza unei conversii predefinite cu pierdere de
informatii, potrivire pe baza unei conversii definite de utilizator (a se
vedea lectia ***); evident, daca la o etapa se vor detecta mai multe alegeri
la fel de valide, se va semnala eroare de ambiguitate, iar daca dupa
parcurgerea etapelor nu s-a putut face nici o alegere se va semnala de
asemenea eroare.
Mai exact, un obiect poate fi creat:
- La intalnirea unei definitii de variabile obiect, statice sau automatice:
<Nume clasa> <Nume obiect>;
<Nume clasa> <Nume obiect> (<Expresie 1>,...,<Expresie n>);
<Nume clasa> <Nume obiect> = <Expresie>;
ultima varianta este echivalenta cu:
<Nume clasa> <Nume obiect> (<Expresie>);
In primul caz, compilatorul va alege un constructor implicit (i.e. unul
care se poate apela fara parametri actuali, cum ar fi unul cu lista vida de
parametri sau unul cu toti parametrii impliciti). In celelalte cazuri,
compilatorul va alege un constructor cu n parametri formali, compatibili ca
tip cu <Expresie 1>,...,<Expresie n>, respectiv un constructor cu un
parametru formal, compatibil ca tip cu <Expresie>; pentru stabilirea
compatibilitatii se respecta regulile/etapele de la supraincarcarea
functiilor in general.
Un comentariu similar este valabil in cazul intalnirii unei definitii de
atribut static care este la randul sau obiect (a se vedea lectia 4,
capitolul IV):
<Nume clasa atribut> <Nume clasa>::<Nume atribut>;
<Nume clasa atribut> <Nume clasa>::<Nume atribut> (<Expr.1>,...,<Expr.n>);
<Nume clasa atribut> <Nume clasa>::<Nume atribut> = <Expresie>;
ultima varianta fiind echivalenta cu:
<Nume clasa atribut> <Nume clasa>::<Nume atribut> (<Expresie>);

- La intalnirea unei definitii de vector de obiecte, static sau automatic:


<Nume clasa> <Nume vector de obiecte> [<Dimensiune>];
<Nume clasa> <Nume vector de obiecte> [<Dim.>] = {<Expr.0>,...,<Expr.n-1>};
<Nume clasa> <Nume vector de obiecte> [] = {<Expr.0>,...,<Expr.n-1>};
In primul caz, pentru fiecare componenta a vectorului de obiecte
compilatorul va alege un constructor implicit. In celelalte cazuri,
pentru componanta de indice 'i' va alege un constructor cu un parametru,
compatibil ca tip cu '<Expr.i>'.
Notam urmatoarele:
* Daca clasa 'Nume clasa' are constructori cu un parametru definiti de
utilizator, pentru a putea utiliza ultimele doua variante de definire a
vectorului de obiecte, atunci ea nu satisface definitia structurii
agregat din lectia 4 si s-ar putea ca unele compilatoare sa nu accepte
aceste variante; teste efectuate cu gcc/g++ sub Linux, versiunea 4.7.2,
au aratat ca acest compilator accepta si asemenea variante.
* In cazul celei de-a doua variante, daca <Dim.> este mai mare decat n,
atunci pentru componentele vectorului de indici 0, ..., n-1 se folosesc
constructorii cu un parametru compatibil cu respectiv <Expr.0>,...,
<Expr.n-1>, iar pentru ultimele componente constructori impliciti.
Un comentariu similar este valabil in cazul intalnirii unei definitii de
atribut static care este la randul sau vector de obiecte:
<Nume
<Nume
=
<Nume
>};

clasa atribut> <Nume clasa>::<Nume atribut> [<Dimensiune>];


clasa atribut> <Nume clasa>::<Nume atribut> [<Dim.>]
{<Expr.0>,...,<Expr.n-1>};
clasa atribut> <Nume clasa>::<Nume atribut> [] = {<Expr.0>,...,<Expr.n-1

- La intalnirea unei cereri de alocare dinamica a unui obiect,


cu operatorul 'new:
new <Nume clasa>
new <Nume clasa> (<Expresie 1>,...,<Expresie n>)
new <Nume clasa> = <Expresie>
ultima varianta este echivalenta cu:
new <Nume clasa> (<Expresie>)
In primul caz, va fi creat un obiect dinamic iar pentru el compilatorul
alege un constructor implicit.
In celelalte cazuri, va fi creat un obiect dinamic iar pentru el este ales
un constructor cu n parametri formali, compatibili ca tip cu
<Expresie 1>,...,<Expresie n>, respectiv un constructor cu un parametru
formal, compatibil ca tip cu <Expresie>.
Operatorul 'new' returneaza adreasa obiectului dinamic creat, ca valoare
de tip '<Nume clasa> *'.
- La intalnirea unei cereri de alocare dinamica a unui vector de obiecte,
cu operatorul 'new:
new <Nume clasa> [<Dimensiune>]
In acest caz, va fi creat dinamic un vector de obiecte cu <Dimensiune>
componente, si pentru fiecare dintre aceste obiecte este ales un
constructor implicit; nu avem posibilitatea de a cere folosirea unui

constructor cu parameri.
Operatorul 'new' returneaza adresa de inceput a vectorului dinamic creat,
ca valoare de tip '<Nume clasa> *'.
- La intalnirea unui apel de functie cu parametri obiect transmisi prin
valoare:
<Nume functie> (<Expresie 1>,...,<Expresie n>)
Atunci, daca functia aleasa de compilator (urmand regulile/etapele de la
supraincarcarea functiilor) are lista de parametri formali
<Parametru formal 1>, ..., <Parametru formal n>
iar dintre acestia <Parametru formal i1>, ..., <Parametru formal ik> sunt
parametri prin valoare care sunt obiecte, ei se vor instantia ca obiecte
automatice locale acestui apel, iar pentru fiecare <Parametru formal ij>
va fi ales un constructor avand un singur parametru formal, compatibil
ca tip cu <Expresie ij> (el va primi aceasta expresie ca parametru actual).
- La crearea altui obiect, in care obiectul in cauza apare ca atribut
nestatic.
Am vazut in lectia 4, capitolul IV, ca un atribut nestatic se instantiaza
la fiecare instantiere a clasei, instanta sa fiind parte a obiectului
respectiv; de aceea, definitia sa este parte a definitiilor obiectelor
clasei din care face parte ca atribut; ea consta din cate un initializator
scris in listele de initializare ale constructorilor acestei clase,
initializatorul la randul lui constand din numele atributului si o
lista de parametri actuali intre paranteze rotunde:
<Nume clasa gazda>::<Nume clasa gazda> (<Parametri formali>):
<Initializator 1>, ..., <Initializator atribut>, ...,<Initializator n>
{<corp>}
unde <Initializator atribut> poate fi:
<Nume atribut> (<Expresie 1>,...,<Expresie n>)
sau poate lipsi.
Atunci, daca obiectul clasei gazda este creat folosind constructorul de mai
sus, atributul sau <Nume atribut> va fi creat folosind un constructor cu n
parametri, compatibili ca tip cu <Expresie 1>,...,<Expresie n>, respectiv
(in cazul cand initializatorul lipseste) un constructor implicit.
- La crearea altui obiect, avand un atribut nestatic care este vector de
obiecte.
Atunci, la fiecare instantiere a clasei gazda se instantiaza si vectorul
(ca parte a obiectului creat), iar pentru fiecare componenta a sa se va
folosi un constructor implicit.
Notam ca in cazul crearii vectorilor de obiecte (statici, automatici,
dinamici), ordinea in care sunt considerate componentele pentru a li se
aplica constructorii este in general de la cel de indice 0 incolo (poate insa
sa depinda de compilator).
Notam de asemenea ca apelul unui constructor, respectiv destructor, se
considera efectuat in domeniul (scope) in care a fost plasata sintagma care
creaza obiectul, respectiv la sfarsitul acestui domeniu. De aceea, pentru a
nu avea probleme privind accesibilitatea, se recomanda sa facem constructorii
si destructorii publici.

Exemplu:
========
#include<iostream>
using namespace std;
class cls{
static int genid;
int id;
public:
cls();
cls(int);
cls(int, int);
~cls();
};
cls::cls(){
id = genid++;
cout << "Ctor cls::cls(), id: " << id << endl;
}
cls::cls(int x){
id = genid++;
cout << "Ctor cls::cls(" << x << "), id: " << id << endl;}
cls::cls(int x, int y){
id = genid++;
cout << "Ctor cls::cls("<< x<<", "<<y << "), id: " << id << endl;}
cls::~cls()
{cout << "Dtor cls::~cls(), id: " << id << endl;}
int cls::genid = 0;
cls a, b(1), c = 2, d(3, 4);
void f() {cls e(5), f(6, 7);}
int main(){
cout << "---\n";
cls g(8), h(9), *p, *q;
p = new cls(10);
q = new cls(11, 12);
delete p;
cout << "---\n";
for(int i = 0; i < 2; ++ i){
cls m(13);
for(int j = 0; j < 2; ++ j) f();
}
cout << "---\n";
}
/* Afisaza:
Ctor cls::cls(), id: 0
Ctor cls::cls(1), id: 1
Ctor cls::cls(2), id: 2
Ctor cls::cls(3, 4), id: 3
--Ctor cls::cls(8), id: 4

Ctor
Ctor
Ctor
Dtor
--Ctor
Ctor
Ctor
Dtor
Dtor
Ctor
Ctor
Dtor
Dtor
Dtor
Ctor
Ctor
Ctor
Dtor
Dtor
Ctor
Ctor
Dtor
Dtor
Dtor
--Dtor
Dtor
Dtor
Dtor
Dtor
Dtor

cls::cls(9), id: 5
cls::cls(10), id: 6
cls::cls(11, 12), id: 7
cls::~cls(), id: 6
cls::cls(13), id: 8
cls::cls(5), id: 9
cls::cls(6, 7), id: 10
cls::~cls(), id: 10
cls::~cls(), id: 9
cls::cls(5), id: 11
cls::cls(6, 7), id: 12
cls::~cls(), id: 12
cls::~cls(), id: 11
cls::~cls(), id: 8
cls::cls(13), id: 13
cls::cls(5), id: 14
cls::cls(6, 7), id: 15
cls::~cls(), id: 15
cls::~cls(), id: 14
cls::cls(5), id: 16
cls::cls(6, 7), id: 17
cls::~cls(), id: 17
cls::~cls(), id: 16
cls::~cls(), id: 13
cls::~cls(),
cls::~cls(),
cls::~cls(),
cls::~cls(),
cls::~cls(),
cls::~cls(),

id:
id:
id:
id:
id:
id:

5
4
3
2
1
0

*/
Comentarii:
- La fiecare creare a unui nou obiect, constructorul apelat asigneaza
acestuia o identificare unica 'id' calculata cu ajutorul atributului
static 'genid', care se incrementeaza de fiecare data.
- La fiecare creare a unui nou obiect se apeleaza un constructor
ales de compilator dupa regulile generale de la supraincarcarea functiilor
privind compatibilitatea ca numar si tip intre parametrii actuali (care
insotesc definitia obiectului) si cei formali (din declaratia
constructorului); de exemplu pentru 'cls a' s-a apelat constructorul fara
parametri, pentru 'b(1)' si 'c = 2' cel cu un parametru, pentru 'd(3, 4)'
cel cu doi parametri.
Notam echivalenta intre a scrie 'cls c = 2' si 'cls c(2)'.
- Pentru fiecare obiect, la inceputul domeniului sau de existenta i s-a
apelat un constructor, la sfarsitul domeniului sau de existenta i s-a
destructorul, iar domeniile de existenta ale obiectelor sunt in
concordanta cu clasa lor de alocare:
* 'a', 'b', 'c', 'd' sunt alocate static, deci au fost create la inceputul
executarii programului, inainte de 'main', in ordinea definirii lor, si
au fost distruse la sfarsitul executarii programului, dupa 'main', in
ordinea inversa crearii lor.
* 'g', 'h' sunt alocate automatic in 'main', deci au fost create dupa
intrarea in apelul lui 'main', la intalnirea definitiei lor (deci dupa ce
s-a afisat "---"), in ordinea intalnirii acestor definitii, si au fost
distruse la iesirea din apelul lui 'main' (deci inainte de distrugerea
obiectelor statice 'a', 'b', 'c', 'd'), in ordinea inversa crearii lor.
* 'm' este alocat automatic in instructiunea compusa ciclata de 'for',

deci la fiecare executare a acestei instructiuni a fost creat la inceput


si distrus la sfarsit (in total 2 creari si 2 distrugeri).
* 'e', 'f' sunt alocate automatic in functia 'f', deci la fiecare apel al
acesteia, cand s-a executat corpul functiei si s-au intalnit definitiile
lor au fost create (in ordinea intalnirii acestor definitii) iar cand s-a
iesit din apel au fost distruse, in ordinea inversa crearii lor (in total,
fiecare a fost creata si distrusa de cate 2 ori).
Intrucat apelul 'f()' este plasat in instructiunea compusa ciclata de
'for(int i...' dupa definitia lui 'm', la fiecare iteratie mai intai a
fost creat 'm', apoi, de doua ori (conform ciclului 'for(int j...'), au
fost create 'e', 'f', apoi au fost distruse 'f', 'e', apoi a fost
distrus 'm'.
* La intalnirea instructiunilor 'p = new cls(10);', 'q = new cls(11, 12);'
au fost create 2 obiecte dinamice (in ordinea executarii acestor
instructiuni) iar la 'delete p;' a fost distrus primul dintre ele.
Pentru al doilea nu am facut 'delete', asa ca el nu s-a distrus in
cadrul executarii programului (obiectele dinamice nu se distrug automat
ci doar la cerere); evident, la terminarea programului ca proces,
sistemul de operare a eliberat resursele (inclusiv de memorie) ocupate
de acesta (nu a ramas undeva in sistem obiectul dinamic respectiv), dar
pe perioada executarii programului nu s-a eliberat memoria ocupata de
acest obiect si nu s-a executat destructorul pentru el.
* 'p' si 'q' nu sunt obiecte ale clasei 'cls' ci pointeri, asa ca desi
ele au fost create/distruse conform clasei lor de alocare (automatice in
'main') pentru ele nu s-a apelat constructor/destructor din 'cls'.
Urmatorul exemplu ilustreaza alte aspecte legate de ordinea crearii/
distrugerii obiectelor statice:
Exemplu:
========
#include<iostream>
using namespace std;
class cls{
int n;
public:
cls(int);
~cls();
};
cls::cls(int x):n(x){cout << "Ctor " << n << endl;}
cls::~cls(){cout << "Dtor " << n << endl;}
void f(){
cout << "Incepe 'f':\n";
static cls a(0);
}
cls b(1);
cls c(2);
int main(){
cout << "Incepe 'main':\n";
f();f();
cout << "Incepe 'for':\n";
for(int i=0;i<3;++i){
cout << "Incepe '{}'\n";
static cls d(3);
cout << "---\n";

static cls e(4);


}
cout << "Se termina 'main'.\n";
}
/* Afisaza:
Ctor 2
Ctor 3
Ctor 4
Incepe 'main':
Incepe 'f':
Ctor 0
Ctor 1
Incepe 'f':
Incepe 'for':
Incepe '{}'
Ctor 5
--Ctor 6
Incepe '{}'
--Incepe '{}'
--Se termina 'main'.
Dtor 6
Dtor 5
Dtor 1
Dtor 0
Dtor 4
Dtor 3
Dtor 2
*/
Comentariu: constatam ca obiectele statice globale au fost create la
inceputul programului in ordinea definirii lor, cele statice locale in
momentul primei executari a blocului in care au fost definite (nu si la
urmatoarele executari ale acestuia), la intalnirea definitiei lor (deci
dupa ce s-a afisat "Incepe..."), iar la sfarsitul programului au fost
distruse toate obiectele statice in ordinea inversa crearii lor in general,
indiferent daca erau globale, locale in 'f' sau in instructiunea compusa
din 'main'.
Urmatorul exemplu ilustreaza alte aspecte legate de ordinea crearii/
distrugerii obiectelor automatice:
Exemplu:
========
#include<iostream>
using namespace std;
class cls{
int n;
public:
cls(int);
~cls();
};
cls::cls(int x):n(x){cout << "Ctor " << n << endl;}
cls::~cls(){cout << "Dtor " << n << endl;}

void f(cls a, cls b,


cout << "=== 'f' 1
cls e(111);
cout << "=== 'f' 2
cls f(222);
cout << "=== 'f' 3
}

cls c){
===\n";
===\n";
===\n";

int main(){
cout << "=== 'main' 1 ===\n";
cls a(1);
cout << "=== 'main' 2 ===\n";
cls b(2);
cout << "=== 'main' 3 ===\n";
f(10,20,30);
f(100,200,300);
cout << "=== 'main' 4 ===\n";
for(int i = 0; i < 2; ++i){
cout << "=== 'for' 1 ===\n";
cls c(3);
cout << "=== 'for' 2 ===\n";
cls d(4);
cout << "=== 'for' 3 ===\n";
}
cout << "=== 'main' 5 ===\n";
}
/* Afisaza:
=== 'main' 1 ===
Ctor 1
=== 'main' 2 ===
Ctor 2
=== 'main' 3 ===
Ctor 30
Ctor 20
Ctor 10
=== 'f' 1 ===
Ctor 111
=== 'f' 2 ===
Ctor 222
=== 'f' 3 ===
Dtor 222
Dtor 111
Dtor 10
Dtor 20
Dtor 30
Ctor 300
Ctor 200
Ctor 100
=== 'f' 1 ===
Ctor 111
=== 'f' 2 ===
Ctor 222
=== 'f' 3 ===
Dtor 222
Dtor 111
Dtor 100
Dtor 200
Dtor 300
=== 'main' 4 ===

=== 'for' 1 ===


Ctor 3
=== 'for' 2 ===
Ctor 4
=== 'for' 3 ===
Dtor 4
Dtor 3
=== 'for' 1 ===
Ctor 3
=== 'for' 2 ===
Ctor 4
=== 'for' 3 ===
Dtor 4
Dtor 3
=== 'main' 5 ===
Dtor 2
Dtor 1
*/
Comentariu: constatam ca obiectele automatice se creaza/distrug la fiecare
executare a blocului in care au fost definite; ele se creaza la intalnirea
definitiei, se distrug la sfarsitul blocului, iar ordinea in care se distrug
este inversa celei in care au fost create:
'a', 'b' se creaza/distrug o data, la singura executare a lui 'main';
'a', 'b', 'c', 'e', 'f' se creaza/distrug de doua ori, la cele doua
executari ale lui 'f';
'c' si 'd' se creaza/distrug de doua ori, la cele doua executari ale
instructiunii compuse.
In cazul lui 'f', parametrii au fost creati la inceputul apelului (deci
inainte de crearea lui 'e', 'f'), in ordinea inversa declararii:
'c', 'b', 'a', apoi s-a executat apelul si la intalnirea definitiilor
lui 'e' si 'f' s-au creat aceste obiecte, iar la sfarsitul apelului
au fost distruse obiectele automatice create cu ocazia acestui apel
in ordinea inversa crearii lor in general, indiferent daca erau
variabile locale sau parametri: 'f', 'e', 'a', 'b', 'c'.
Pentru alocarea dinamica (i.e. la cerere) de memorie, in C++ avem la
dispozitie atat functiile de biblioteca din C: 'malloc', 'calloc', 'realloc',
'free', cat si operatorii specifici C++: 'new', 'delete'. Prezentam mai jos
anumite particularitati legate de acestea:
- Functiile 'malloc', 'calloc', 'realloc', simplist vorbind, primesc ca
parametru o dimensiune de memorie, aloca zona respectiva retinand intr-o
tabela interna adresa de inceput si dimensiunea acesteia, si returneaza
adresa de inceput a zonei, ca 'void *'; in C, aceasta poate fi atribuita
direct oricarei variabile pointer fara cast; in C++, ea nu poate fi
atribuita unei variabile pointer la non-void decat cu cast; in continuare,
adresa poate fi atribuita de la un pointer la altul, eventual cu cast,
de fiecare data ne transmitandu-se si vreo indicatie despre dimensiunea
zonei; pentru a pasa aceasta adresa ca parametru lui 'free' (in scopul
dezalocarii zonei) nu este nevoie sa folosim aceeasi variabila pointer
in care am primit adresa de la 'malloc'/'calloc'/'realloc', nici macar
nu este nevoie sa fie o variabila de acelasi tip pointer - practic, se
se va transmite doar o adresa; 'free' va cauta adresa in tabela interna
si de acolo va determina dimensiunea zonei ce trebuie dezalocate (evident,
va elimina din tabela adresa si dimensiunea de memorie asociata); de
aceea, lui 'free' nu trebuie sa-i dam ca parametru decat o adresa
returnata in prealabil de 'malloc'/'calloc'/'realloc', altfel nu o
gaseste in tabela interna iar comportamentul programului este imprevizibil;
la fel se intampla daca apelam 'free' cu aceeasi adresa de mai multe ori

(la primul apel 'free' gaseste adresa in tabela, la urmatoarele nu).


Notam de asemenea ca un urma unui apel de forma 'free(p)', unde 'p' este
o variabila pointer, variabila 'p' nu-si pierde valoarea - ea pointeaza
in continuare in acelasi loc, chiar daca zona respectiva a fost dezalocata,
si permite acceasarea ei in continuare; daca ulterior executam un alt
'malloc'/'calloc'/'realloc' aceasta zona sau o parte a ei poate fi
incorporata in zona nou alocata si atunci cu 'p' putem modifica intr-un
mod posibil nedorit/neasteptat zona respectiva, pe alta cale decat prin
pointerii asociati la noua alocare.
Exemplu:
========
#include<iostream>
#include<stdlib.h>
using namespace std;
int main(){
int *p; double *q; int a;
p = (int *) malloc(10 * sizeof(int));
// daca 'sizeof(int)' este 4, atunci se aloca 40 octeti,
// de la o adresa 'alpha'; in limbajul C nu este necesar
// castul '(int *)';
// intr-o tabela interna, se retine perechea (alpha, 40);
q = (double *) p;
// se atribuie doar 'alpha'; este posibil d.p.v. tehnic, deoarece
// in locatiile pointerior se retin doar adresele de memorie, iar
// la nivel fizic toate adresele sunt la fel (adrse de octeti);
// castul explicit este necesar doar pentru a primi acceptul
// compilatorului (in absenta lui se semnaleaza eroare de compilare);
free(q);
// se paseaza ca parametru actual doar 'alpha'; functia 'free'
// cauta 'alpha' in tabela interna, gaseste asociat 40, si
// dezaloca 40 octeti de la adresa 'alpha';
// observam ca la 'free' nu a fost nevoie sa folosim tot 'p'
// (ca la 'malloc') si nici macar un pointer de acelasi tip
// ('p' este 'int *', 'q' este 'double *');
// in urma acestui 'free', 'q' isi pastreaza valoarea 'alpha' (si la
// fel 'p');
free(q);
// comportament imprevizibil, de exemplu terminare anormala a
// programului, deoarece 'alpha' nu mai este in tabela interna
free(&a);
// comportament imprevizibil, de exemplu terminare anormala a
// programului deoarece adresa lui 'a' nu este (n-a fost niciodata)
// in tabela interna
int *s; long long int *t;
// tipul 'long long int' este disponibil in 'gcc/g++';
// in versiunea considerata de 'g++', 'int' si 'long long int'
// inseamna intreg cu semn de 4, respectiv 8 octeti
s = (int *) malloc(sizeof(int));
// se aloca 4 octeti de la o adresa 'beta'; in tabela interna se
// retine perechea (beta, 4), in 's' se retine 'beta'
free(s);
// se elibereaza cei 4 octeti de la adresa 'beta'; in tabela interna nu
// mai apare 'beta' (acum este adresa libera), dar 's' retine in
// continuare 'beta'
t = (long long int *) malloc(sizeof(long long int));
// se aloca 8 octeti; intamplator (nu este obligatoriu), sunt tot de la

// adresa 'beta'; in tabela interna se retine perechea (beta, 8), in


// 't' se retine 'beta';
// acum 't' si 's' pointeaza aceeasi zona
cout << hex;
// setam 'cout' a.i. in continuare toate valorile intregi sa se afiseze
// hexa
cout << t << " " << s << endl;
// se afisaza adresele retinute in 't' si 's', anume 'beta' (vedem ca
// cele doua adrese sunt egale);
// de exemplu se afisaza: 0x9901038 0x9901038
*t = 0x0000000100000001ll;
// atribuim o valoare 'long long' zonei pointate de 't'; fara 'll'
// literalul este considerat implicit de tip 'int' iar valoarea
// sa ar putea fi considerata prea mare
cout << *t << endl;
// afisaza: 100000001
*s = 0x00000002;
// atribuim o valoare 'int' zonei pointate de 's'; practic, se
// suprascrie jumatatea low a zonei pointete de 't'
cout << *t << endl;
// afisaza: 100000002
// observam ca continutul zonei pointete de 't' s-a modificat, desi nu
// am folosit '*t'
free(s);
// 'free' primeste ca parametru 'beta', gaseste in tabela interna
// asociat 8 octeti si dezaloca zona de 8 octeti de la adresa 'beta';
// astfel, zona este dezalocata corect (cu dimensiunea cu care s-a
// alocat la 't = (long long int *) malloc(sizeof(long long int))'),
// desi n-am folosit 'free(t)'
free(t);
// comportament imprevizibil, de exemplu terminare anormala a
// programului, deoarece 'beta' nu mai este in tabela interna
}
Daca dorim sa folosim 'malloc'/'calloc'/'realloc' pentru a aloca dinamic
un obiect sau vector de obiecte, aceste functii vor aloca doar memoria
necesara locatiei acestora (conform 'sizeof'-ului clasei), fara a le apela
vreun constructor - astfel, nu se vor face anumite initializari si nu se vor
aloca resurse suplimentare, de exemplu alte zone de memorie alocate dinamic
pentru care in locatia obiectelor se retine (in niste atribute) doar adresa
de inceput.
Corespunzator, 'free' dezaloca doar memoria alocata locatiei obiectelor,
fara a le apela destructorul - astfel, daca de exemplu obiectele detineau
zone de memorie alocate dinamic pentru care in locatie se retinea doar
adresa, aceste zone vor ramane ne dezalocate (si vor ocupa heap-ul).
Exemplu:
========
#include<iostream>
#include<stdlib.h>
using namespace std;
class cls{
void *p;
public:
cls();
~cls();
void set(void *);
};

cls::cls() {p = NULL; cout << "Ctor\n";}


cls::~cls() {free(p); cout << "Dtor\n";}
void cls::set(void *x) {free(p); p = x;}
int main(){
cls *a, *b; int *n;
a = (cls *) malloc(sizeof(cls));
// se aloca sizeof(cls) octeti de memorie de la o adresa 'alpha',
// cat pentru un obiect al clasei 'cls', dar nu i se apeleaza
// acestuia constructorul;
// in particular, atributul 'p' al obiectului nu primeste valoarea NULL
// (ramane cu o valoare implicita, conform clasei sale de alocare
// dinamica) si nu se afisaza nimic;
n = (int *) malloc(sizeof(int)); *n = 10;
// se aloca sizeof(int) octeti de memorie de la o adresa 'beta',
// cat pentru o variabila de tip 'int', si se initializeaza aceasta
// variabila cu 10;
a -> set(n);
// mai intai se apeleaza 'free' pentru adresa necontrolata continuta in
// atributul 'p', efectul fiind imprevizibil; in continuare, daca
// programul nu s-a terminat anormal, se executa 'p = x' asa incat in
// final obiectul dinamic pointeaza cu atributul 'p' zona de la
// adresa 'beta';
free(a);
// se dezaloca cei sizeof(cls) octeti de la adresa 'alpha', adica
// obiectul dinamic, dar nu i se apeleaza acestuia destructorul;
// in particular, zona dinamica de la adresa 'beta' (pointata de
// atributul 'p' al obiectului) ramane nedezalocata si nu se afisaza
// nimic;
b = (cls *) malloc(3 * sizeof(cls));
// se aloca memorie cat pentru 3 obiecte ale clasei 'cls', dar nu li se
// apeleaza acestora constructorul; atributele 'p' ale acestor obiecte
// cu niste valori implicite, necontrolate, si nu se afisaza nimic;
for(int i=0; i<3; ++i){
n = (int *) malloc(sizeof(int)); *n = i * 100;
b[i].set(n);
}
// se apeleaza 'free' pentru adresele necontrolate continute in atributele
// 'p', efectul fiind imprevizibil; daca programul nu s-a terminat
// anormal, in final atributele 'p' ale celor 3 obiecte dinamice vor
// contine adresele 'beta1', 'beta2', 'beta3' ale celor 3 variabile
// dinamice de tip 'int' alocate cu 'malloc';
free(b);
// se dezaloca toate cele 3 obiecte dinamice (in total 3 * sizeof(cls)
// octeti), dar nu li se apeleaza acestora destructorul;
// in particular, zonele dinamice de la adresele 'beta1', 'beta2',
// 'beta3' raman nedezalocate si nu se afisaza nimic;
}
- Operatorul 'new' se poate apela sub formele:
new <Nume tip>
Efect: Se aloca sizeof(Nume tip) octeti de memorie, de la o adresa 'alpha',
cat pentru o instanta a tipului <Nume tip>; daca <Nume tip> nu este clasa,
instanta (data) are o valoare nedeterminata (ce se afla in memoria alocata
in acel moment), desi anumite compilatoare, compatibile cu anumite

standarde C++, pot forta initializarea cu 0; daca <Nume tip> este clasa,
instantei (obiectului) i se aplica un constructor implicit (i.e. care se
poate apela fara parametri actuali) (iar daca clasa nu are un asemenea
constructor, se semnaleaza eroare); intr-o tabela interna se retine
perechea ('alpha', sizeof(<Nume tip>); se returneaza adresa zonei alocate
'alpha', ca valoare de tip '<Nume tip> *'.
new <Nume tip> = <Expresie> (echivalent cu: new <Nume clasa> (<Expresie>) )
Efect: similar ca mai sus, dar daca daca <Nume tip> nu este clasa, instanta
(data) este initializata cu valoarea lui <Expresie>, iar daca <Nume tip>
este clasa, instantei (obiectului) i se aplica un constructor cu un
parametru compatibil cu <Expresie>, primind expresia ca parametru actual
(iar daca clasa nu are un asemenea constructor, se semnaleaza eroare).
new <Nume tip> (<Expresie 1>,...,<Expresie n>)
Efect: daca n > 1, aceasta varianta se poate folosi doar daca <Nume tip>
este o clasa; efectul este similar ca mai sus, dar instantei (obiectului)
i se aplica un constructor cu n parametri compatibil cu <Expresie1>, ...,
<Expresie n>, primind aceste expresii ca parametri actuali (iar daca clasa
nu are un asemenea constructor, se semnaleaza eroare).
new <Nume tip> [<Dimensiune>]
Efect: Se aloca <Dimensiune> * sizeof(Nume tip) octeti de memorie, de la o
adresa 'alpha', cat pentru n instante ale tipului <Nume tip> amplasate
adiacent, sub forma unui vector; daca <Nume tip> nu este clasa, toate
componentele vectorului au o valoare nedeterminata, desi anumite
compilatoare, compatibile cu anumite standarde C++, pot forta
initializarea lor cu 0; daca <Nume tip> este clasa, componentelor
vectorului (obiectelor) li se aplica un constructor implicit (iar daca
clasa nu are un asemenea constructor, se semnaleaza eroare); intr-o tabela
interna se retine perechea ('alpha', n * sizeof(<Nume tip>); se returneaza
adresa zonei alocate 'alpha', ca valoare de tip '<Nume tip> *'.
Sunt intalnite si formele urmatoare:
new <Nume tip> ()
new <Nume tip> [<Dimensiune>] ()
care sunt folosite pentru a forta initializarea cu 0, respectiv folosirea
constructorului implicit; prima varianta este utila in cazul cand
<Nume tip> nu este clasa iar forma 'new <Nume tip>' nu forteaza
initializarea cu 0.
Notam ca in cazul alocarii de vectori: 'new <Nume tip> [<Dimensiune>]',
pentru componentele vectorului nu putem specifica o valoare de
initializare nenula, respectiv un alt constructor decat cel implicit.
Operatorul 'delete' se poate apela sub formele:
delete <Expresie pointer>
delete [] <Expresie pointer>
Efect: Presupunem ca <Expresie pointer> produce o valoare 'alpha' de tip
'<Nume tip> *'; se cauta in tabela interna folosita de 'new' o pereche
('alpha', n); daca nu se gaseste, comportamentul este imprevizibil (de
exemplu terminare anormala a programului); daca se gaseste, se dezaloca
n octeti de la adresa 'alpha', se elimina ('alpha', n) din tabela si:

* daca <Nume tip> este clasa si am folosit forma


'delete <Expresie pointer>', se apeleaza destructorul clasei <Nume tip>
pentru obiectul de la adresa 'alpha';
* daca <Nume tip> este clasa si am folosit forma
'delete [] <Expresie pointer>', se apeleaza destructorul clasei
<Nume tip> de n ori, pentru cele n obiecte adiacente care incep de la
adresa 'alpha';
operatorul 'delete' are tipul returnat 'void' (nu returneaza nimic).
In cazul formelor:
new <Nume tip> [<Dimensiune>]
delete [] <Expresie pointer>
ordinea in care sunt apelati constructorii/destructorii pentru componentele
vectorului poate sa depinda de compilator; de regula la 'new' constructorii
sunt apelati in ordinea crescatoare a indicilor componentelor, iar la
'delete' destructorii sunt apelati in ordinea inversa celei in care s-au
apelat constructorii (deci in ordinea descrescatoare a indicilor).
In toate formele de mai sus, daca 'new' nu poate face alocarea, returneaza
NULL.
- Constatam urmatoarele diferente intre 'malloc'/'calloc'/'realloc'/'free'
si 'new'/'delete':
* 'malloc'/'calloc'/'realloc' doar aloca memorie, in timp ce 'new',
in cazul cand avem de-a face cu obiecte, apeleaza si un constructor;
* 'free' doar dezaloca memoria, in timp ce 'delete', in cazul cand avem
de-a face cu obiecte, apeleaza si destructorul;
* 'malloc'/'calloc'/'realloc' returneaza mereu 'void *', in timp ce 'new'
returneaza o adresa deja castata la tipul de pointer dorit '<Nume tip> *'.
Asadar, daca avem de-a face cu date care nu sunt obiecte, nu exista
diferente majore intre 'malloc'/'calloc'/'realloc'/'free' si
'new'/'delete' (le putem folosi pe oricare); daca avem de-a face cu
obiecte, 'new'/'delete' sunt in avantaj; de aceea, pentru a lucra unitar
si a respecta stilul de lucru obiectual, se recomanda ca in toate cazurile
(si daca avem de-a face cu obiecte si daca nu avem de-a face cu obiecte) sa
se foloseasca 'new'/'delete'.
Notam ca nu exista un echivalent operatorial, in stil C++, al lui 'realloc';
el poate fi insa simulat, de exemplu daca avem:
typedef int T;
const int n1 = 2, n2 = 4;
int i;
T *p, *q;
atunci codul:
p = (T *) calloc(n1, sizeof(T));
for(i = 0; i < n1; ++i) p[i] = i * 10;
q = (T *) realloc(p, n2 * sizeof(T));
for(i = n1; i < n2; ++i) q[i] = i * 100;
for(i = 0; i < n2; ++i) cout << q[i] << " ";
cout << endl;
free(q);
poate fi inlocuit cu codul:

p = new T[n1] ();


for(i = 0; i < n1; ++i) p[i] = i * 10;
q = new T[n2];
for(i = 0; i < n1; ++i) q[i] = p[i];
delete [] p;
for(i = n1; i < n2; ++i) q[i] = i * 100;
for(i = 0; i < n2; ++i) cout << q[i] << " ";
cout << endl;
delete [] q;
Notam ca 'realloc' incearca sa redimensioneze zona dinamica veche, iar daca
aceasta nu se poate aloca alta zona, copiaza in ea informatia din zona
veche si dezaloca zona veche; cand lucram cu 'new'/'delete' copierea
informatiei si dezalocarea zonei vechi trebuie facute explicit.
Daca 'T' ar fi fost o clasa simularea ar fi fost mai dificila, deoarece
alocarea/copierea presupun apelarea unor constructori si operatori de
atribuire (exercitiu !).
In alte privinte, modul de functionare al lui 'malloc'/'calloc'/'realloc'/
'free' si 'new'/'delete' se aseamana: si aici se foloseste o tabela
interna, 'new' retine in ea adresa si dimensiunea zonei returnand doar
adresa, aceasta adresa poate fi atribuita (eventual cu cast) unor pointeri
diversi (nu neaparat spre acelasi tip) fara a se transmite si dimensiunea
zonei, 'delete' primeste ca operand doar adresa (nu si dimensiunea) si
determina dimensiunea zonei din tabela interna (in particular trebuie sa
gaseasca adresa acolo, altfel comportamentul este imprevizibil), eliminand
adresa din tabela; daca operandul lui 'delete' este o variabila pointer, ea
isi pastreaza in continuare valoarea, pointand zona respectiva chiar daca
este dezalocata (iar ulterior folosita partial sau total in alte alocari).
In plus, pot aparea probleme suplimentare legate de apelarea
constructorilor/destructorilor necorespunzatori. De exemplu, ca si in cazul
lui 'free', cand adresa ajunge la 'delete' ea nu trebuie neaparat sa aiba
acelasi tip de baza ca atunci cand a fost returnata de 'new'; 'delete' va
dezaloca corect exact cat a alocat 'new' (dimensiunea zonei este luata din
tabela interna), dar pentru zona respectiva va apela destructorul noului
tip de baza.
Urmatorul exemplu reia primul exemplu legat de 'malloc'/'calloc'/'realloc'/
'free' pentru a ilustra fenomene asemanatoare legate de 'new'/'delete':
Exemplu:
========
#include<iostream>
using namespace std;
int main(){
int *p; double *q; int a;
p = new int [10];
// daca 'sizeof(int)' este 4, atunci se aloca 40 octeti,
// de la o adresa 'alpha'; observam ca nu este necesar
// castul '(int *)' ('new' returneaza adresa gata castata);
// intr-o tabela interna, se retine perechea (alpha, 40);
// 'int' nefiind o clasa, nu se apeleaza nici un constructor
q = (double *) p;
// se atribuie doar 'alpha'
delete q;
// se paseaza ca parametru actual doar 'alpha'; 'delete'
// cauta 'alpha' in tabela interna, gaseste asociat 40, si
// dezaloca 40 octeti de la adresa 'alpha';

// observam ca la 'delete' nu a fost nevoie sa folosim tot 'p'


// (ca la 'new') si nici macar un pointer de acelasi tip
// ('p' este 'int *', 'q' este 'double *');
// 'double' nefiind o clasa, nu se apeleaza nici un destructor;
// in urma acestui 'delete', 'q' isi pastreaza valoarea 'alpha'
delete q;
// comportament imprevizibil, de exemplu terminare anormala a
// programului, deoarece 'alpha' nu mai este in tabela interna
delete &a;
// comportament imprevizibil, de exemplu terminare anormala a
// programului deoarece adresa lui 'a' nu este (n-a fost niciodata)
// in tabela interna
int *s; long long int *t;
// tipul 'long long int' este disponibil in 'gcc/g++';
// in versiunea considerata de 'g++', 'int' si 'long long int'
// inseamna intreg cu semn de 4, respectiv 8 octeti
s = new int;
// se aloca 4 octeti de la o adresa 'beta'; in tabela interna se
// retine perechea (beta, 4), in 's' se retine 'beta'
delete s;
// se elibereaza cei 4 octeti de la adresa 'beta'; in tabela interna nu
// mai apare 'beta' (acum este adresa libera), dar 's' retine in
// continuare 'beta'
t = new long long int;
// se aloca 8 octeti; intamplator (nu este obligatoriu), sunt tot de la
// adresa 'beta'; in tabela interna se retine perechea (beta, 8), in
// 't' se retine 'beta';
// acum 't' si 's' pointeaza aceeasi zona
cout << hex;
// setam 'cout' a.i. in continuare toate valorile intregi sa se afiseze
// hexa
cout << t << " " << s << endl;
// se afisaza adresele retinute in 't' si 's', anume 'beta' (vedem ca
// cele doua adrese sunt egale);
// de exemplu se afisaza: 0x95a9038 0x95a9038
*t = 0x0000000100000001ll;
// atribuim o valoare 'long long' zonei pointate de 't'; fara 'll'
// literalul este considerat implicit de tip 'int' iar valoarea
// sa ar putea fi considerata prea mare
cout << *t << endl;
// afisaza: 100000001
*s = 0x00000002;
// atribuim o valoare 'int' zonei pointate de 's'; practic, se
// suprascrie jumatatea low a zonei pointete de 't'
cout << *t << endl;
// afisaza: 100000002
// observam ca continutul zonei pointete de 't' s-a modificat, desi nu
// am folosit '*t'
delete s;
// 'delete' primeste ca parametru 'beta', gaseste in tabela interna
// asociat 8 octeti si dezaloca zona de 8 octeti de la adresa 'beta';
// astfel, zona este dezalocata corect (cu dimensiunea cu care s-a
// alocat la 't = new long long int'), desi n-am folosit 'delete t';
// 'int' nefiind o clasa, nu se apeleaza niciun destructor;
delete t;
// comportament imprevizibil, de exemplu terminare anormala a
// programului, deoarece 'beta' nu mai este in tabela interna
}

Urmatorul exemplu evidentiaza principala diferenta intre 'malloc'/'calloc'/


realloc'/'free' si 'new'/'delete', anume ca ultimii apeleaza constructorii/
destructorii, si ilustreaza aspecte legate de alocarea/dezalocarea dinamica
a vectorilor, fenomene legate de apelarea destructorilor necorespunzatori,
etc.:
Exemplu:
========
#include<iostream>
#include<stdlib.h>
using namespace std;
class cls1{
int n;
public:
cls1();
~cls1();
};
cls1::cls1() {cout << "Ctor cls1 " << this << endl; n = 1;}
cls1::~cls1() {cout << "Dtor cls1 " << this << endl; n = 0;}
class cls2{
int v[11];
public:
cls2();
~cls2();
};
cls2::cls2() {cout << "Ctor cls2 " << this << endl; v[10] = 1;}
cls2::~cls2() {cout << "Dtor cls2 " << this << endl; v[10] = 2;}
int main(){
cls1 *a; cls2 *b;
cout << sizeof(cls1) << " " << sizeof(cls2) << endl;
// aflam dimensiunile obiectelor celor doua clase; se afisaza:
//
4 44
a = new cls1;
// se aloca dinamic memorie cat pentru un obiect al clasei 'cls1'
// (i.e. 4 octeti) si i se aplica constructorul 'cls1::cls1()';
// se afisaza:
//
Ctor cls1 0x9d7b008
// (numarul afisat este adresa obiectului dinamic) iar atributul
// 'n' al obiectului dinamic primeste valoarea 1
delete a;
// se dezaloca obiectul dinamic ca locatie (i.e. 4 octeti) si i se
// apeleaza destructorul; inainte de dezalocare, se afisaza:
//
Dtor cls1 0x9d7b008
// iar atributul 'n' al obiectului primeste valoarea 0
a = new cls1 [3];
// se aloca dinamic memorie cat pentru 3 obiecte ale clasei 'cls1'
// (i.e. 12 octeti) si li se apeleaza acestora constructorul
// 'cls1::cls1()';
// pentru fiecare, se afisaza un mesaj iar atributul 'n' primeste
// valoarea 1; per total, se afisaza:
//
Ctor cls1 0x9d7b01c
//
Ctor cls1 0x9d7b020

//
Ctor cls1 0x9d7b024
// deci, constructorii s-au aplicat obiectelor in ordinea crescatoare
// a adreselor (indicilor)
delete [] a;
// se dezaloca toate cele 3 obiecte dinamice (i.e. 12 octeti) si li se
// apeleaza acestora destructorul; pentru fiecare, inainte de
// dezalocare, se afisaza un mesaj iar atributul 'n' primeste
// valoarea 0; per total, se afisaza:
//
Dtor cls1 0x9d7b024
//
Dtor cls1 0x9d7b020
//
Dtor cls1 0x9d7b01c
// deci, destructorii s-au aplicat componentelor vectorului in ordinea
// inversa celei in care li s-au aplicat constructorii (i.e. in
// ordinea descrescatoare a adreselor/indicilor)
a = new cls1 [3];
// ca mai inainte, se aloca dinamic 3 obiecte ale clasei 'cls1' (12
// octeti) si li se apeleaza constructorul; se afisaza:
//
Ctor cls1 0x9d7b01c
//
Ctor cls1 0x9d7b020
//
Ctor cls1 0x9d7b024
delete a;
// se dezaloca toate cele 3 obiecte alocate inainte (dimensiunea
// 12 octeti este obtinuta din tabela interna), dar se apeleaza
// destructorul doar pentru primul obiect; se afisaza:
//
Dtor cls1 0x9d7b01c
// notam ca pe unele compilatoare, acest 'delete a' (fara '[]')
// provoaca terminarea anormala a programului
a = new cls1;
// se aloca dinamic memorie cat pentru un obiect al clasei 'cls1'
// (i.e. 4 octeti), de la o adresa 'alpha', si i se aplica
// obiectului constructorul 'cls1::cls1()'; in tabela interna se
// retine perechea (alpha, 4) si se afisaza:
//
Ctor cls1 0x9d7b008
int *n = new int; *n = 10;
// se aloca dinamic o variabila 'int' si se atribuie valoarea 10
cout << n << " " << *n << endl;
// se afisaza adresa 'beta' si valoarea variabilei de tip 'int':
//
0x9d7b030 10
// constatam ca 'beta' = 'alpha' + 0x28, adica 'beta' = 'alpha' + 40
b = (cls2 *) a;
// 'b' primeste ca valoare 'alpha'
delete b;
// se va cauta 'alpha' in tabela si gasindu-se asociata valoarea 4, se
// vor dezaloca (corect) cei 4 octeti alocati de la adresa 'alpha'
// (si nu 44, cat ar fi sizeof(cls2));
// inainte de a se efectua dezalocarea insa se apeleaza destructorul
// lui 'cls2' zonei de la adresa 'alpha' (compilatorul insereaza
// in acest loc apelul destructorului clasei lui 'b'); acest
// destructor afisaza:
//
Dtor cls2 0x9d7b008
// dupa care incearca sa gaseasca de la adresa 'alpha' locatia
// care ar corespunde lui 'v[10]', pentru a-i da valoarea
// 2); avand in vedere ca aceasta locatie se afla la distanta
// 10 * sizeof(int) = 40 de inceputul locatiilor obiectelor lui
// 'cls2', se incearca accesarea memoriei de la adresa
// 'alpha' + 40 iesind din limitele zonei de 4 octeti alocati
// de la adresa 'alpha'; intr-o asemenea situatie coportamentul
// poate fi imprevizibil (de exemplu terminarea anormala a

// programului), dar in acest caz a fost nimerita locatia variabilei


// dinamice de tip 'int' (pentru ca 'alpha' + 40 = 'beta') si s-a
// scris acolo valoarea 2 (peste vechiul 10)
cout << *n << endl;
// se afisaza: 2
delete n;
// nu se termina anormal programul, deoarece 'delete b' a dezalocat
// doar 4 octeti de la adresa 'alpha' (conform tabelei interne),
// nu si zona de 4 octeti de la adresa 'beta' (desi destructorul
// apelat a suprascris acolo o valoare)
}
Exemplele de mai sus ne arata ca manevrarea neatenta a instrumentelor de
exploatare a memoriei poate conduce la fenomene nedorite. De regula aceste
fenomene se refera la pointeri salbatici (wild pointers), pointeri agatati
(dangling pointer), scurgere de memorie (memory leak).
Pointerii wild si cei dangling sunt pointeri ce nu pointeaza o entitate
valida la momentul potrivit:
- Un POINTER SALBATIC (WILD POINTER) este un pointer folosit inainte de
initializarea sa cu o valoare cunoscuta (practic, un pointer neinitializat);
strict vorbind, orice pointer creat printr-o definitie ce nu forteaza o
initializare isi incepe existenta ca pointer wild. Deferentierea lui
inseamna accesarea unei locatii aflate la o adresa necontrolata, ceea ce
poate avea efecte imprevizibile: accesarea locatiei unei variabile a
programului nimerita accidental, accesarea unei zone interzise, soldata cu
terminarea anormala a programului, etc.
De cele mai multe ori pointerii wild apar pentru ca sarim peste
initializarea lor si nu pentru ca o omitem.
Exemplu:
========
void f(int i) {
int *p;
// 'p' este un pointer wild, deoarece variabilele automatice au
// implicit o valoare nedeterminata (de exemplu valoarea aflata pe
// stiva in locul unde li s-a amplasat locatia)
static int *q;
// 'q' nu este un pointer wild, deoarece variabilele statice sunt
// initializate cu 0 la crearea lor, iar ulterior isi pastreaza
// valoarea de la ultimul apel
if (i > 0) q = p = (int *) malloc(sizeof(int));
*p = i;
// daca i <= 0, 'p' este in continuare pointer wild
// iar efectul este imprevizibil
free(p);
// daca i <= 0, 'p' este in continuare pointer wild
// iar efectul este imprevizibil
free(q);
// chiar daca am sarit peste 'malloc' iar 'q' ramane NULL,
// e OK, deoarece are sens 'free(NULL)'
}
- Un POINTER AGATAT (DANGLING POINTER) apare atunci cand o zona de memorie
este dezalocata explicit (cu 'free'/'delete', daca zona era alocata
dinamic) sau prin distrugerea cadrului de stiva din care facea parte, la
revenirea din apelul functiei/blocului respectiv (daca zona era alocata
automatic), fara a modifica valorile pointerilor catre ea, a.i. acesti

pointeri inca pointeaza locatia respectiva si permit accesarea ei (in


general, un pointer devine dangling atunci cand zona pointata de el este
dezalocata).
In continuare sistemul poate realoca zona dezalocata in alte scopuri si
atunci, daca deferentiem pointerii care sunt acum dangling pot aparea
efecte imprevizibile, cum ar fi: coruperea unor date care nu au legatura
cu pointerii respectivi, coruperea unor date ale sistemului, care se poate
solda cu instabilitate sau terminare anormala, accesarea unor informatii/
functii la care in mod normal accesul este interzis, avand drept consecinta
goluri de securitate.
Cauze uzuale care conduc la aparitia pointerilor dangling:
* Asignarea adresei unei variabile unui pointer cu domeniu de existenta mai
mare decat al ei; la iesirea din domeniul de existenta al variabilei, ea
este dezalocata iar pointerul devine dangling.
* Returnarea dintr-o functie a adresei unei variabile automatice a sa;
la revenirea in contextul apelant aceasta adresa va corespunde unei zone
dezalocate de pe stiva, iar atribuirea ei unui pointer il face sa fie
dangling.
* Un apel de forma 'free(p)' sau 'delete p', chiar daca elibereaza zona
dinamica pointata de 'p', nu modifica valoarea lui 'p'; el pointeaza in
continuare zona respectiva, ca pointer dangling.
Exemplu:
========
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
int *func(){
int num = 1234;
// 'num' este automatica, locala functiei 'func', se aloca pe stiva
return &num;
}
int main(){
int *p = NULL;
{
int a;
// 'a' este automatica, locala instructiunii compuse, se aloca pe stiva
cout << &a << endl;
// afisaza adresa lui 'a': 0xbff0aa28
p = &a;
} // 'a' iese din domeniul ei de existenta (out of scope)
// 'p' este acum un pointer dangling
{
int b;
cout << &b << endl;
// afisaza adresa lui 'b': 0xbff0aa28
// intamplator, s-a alocat pe stiva acolo unde fusese 'a',
// deci 'p' contine adresa lui
b = 1;
*p = 2;
cout << b << endl; // afisaza: 2
}
int *q = NULL, n1 = 10, n2 = 10;
q = func(); // acum 'num' este dezalocat, asa ca 'q' este dangling
n1 = *q;

printf("Hello !\n");
n2 = *q;
cout << n1 << " " << n2 << endl;
// afisaza: 1234 0
// observam ca o vreme zona pointata de 'q' si-a pastrat valoarea,
// dar un apel ulterior de functie (cel al lui 'printf') a
// refolosit zona respectiva, suprascriind valoarea 1234 de acolo cu 0
int *r = NULL, *s = NULL;
r = (int *) malloc(sizeof(int));
cout << r << endl;
// afisaza adresa zonei alocate: 0x9d3f008
free(r);
// zona pointata de 'r' se elibereaza, 'r' nu-si pierde valoarea
// si devine dangling
s = (int *) malloc(sizeof(int));
cout << s << endl;
// afisaza adresa zonei alocate: 0x9d3f008
// intamplator, este aceeasi zona ca mai inainte,
// deci si 'r' o pointeaza
*s = 1;
*r = 2;
cout << *s << endl; // afisaza: 2
}
Un alt exemplu ar putea fi dat daca un pointer este folosit pentru a efectua
un apel de metoda virtuala iar o alta adresa (posibil a unui cod exploit) ar
putea fi utilizata pentru apel din cauza suprascrierii pointerului 'vtable'
(pentru detalii privind metodele virtuale, a se vedea lectia ***).
De obicei bug-urile cauzate de pointerii dangling sunt mai greu de detectat/
eliminat decat cele cauzate de pointerii wild, unde deferentierea duce de
obicei la terminarea imediata (anormala) a programului.
Solutii uzuale pentru evitarea pointerilor dangling:
* Asignarea pointerilor cu 0 (NULL) imediat ce zona pointata de ei este
dezalocata, sau sa ne asiguram ca pointerii nu vor fi folositi din nou
fara o initializare ulterioara; asignarea cu 0 a unui pointer imediat
ce zona pointata de el este dezalocata nu va seta insa cu 0 automat si
alti pointeri care contin copii ale lui:
int *p, *q;
p = (int *) malloc(sizeof(int));
q = p;
/* ... */
free(p); p = NULL;
// acum 'p' este safe, dar 'q' a ramas dangling
free(q);
// comportament imprevizibil, se incearca dezalocarea
// unei zone deja dezalocate
Asignarea cu 0 nu transforma neaparat programul intr-unul corect, dar
permite identificarea mai rapida a bug-urilor, deoarece deferentierea lui
NULL se soldeaza de obicei cu terminarea imediata (anormala) a
aprogramului.
* Folosirea de POINTERI INTELIGENTI (SMART POINTERS), care folosesc
numararea referirilor (reference counting) pentru a recupera obiectele,
a se vedea:
http://en.wikipedia.org/wiki/Smart_pointer
* Folosirea COLECTORULUI DE GUNOI BOEHM (BOEHM GARBAGE COLLECTOR) in locul
functiilor standard de alocare a memoriei; astfel, sunt evitati complet

pointerii dangling prin faptul ca sunt dezactivate 'free'-urile iar


obiectele sunt recuperate prin 'garbage collection', a se vedea:
http://en.wikipedia.org/wiki/Boehm_garbage_collector
- O SCURGERE DE MEMORIE (MEMORY LEAK) apare atunci cand o zona de memorie
este alocata dar nu pote fi accesata de catre codul care ruleaza.
Ea are simptome similare cu un numar de alte probleme si in general poate
fi diagnosticata doar prin analiza codului de catre programator.
Deoarece scurgerile de memorie pot epuiza memoria disponibila pe masura
ce programul ruleaza, ele sunt un factor de imbatranire software;
IMBATRANIREA SOFTWARE (SOFTWARE AGING) = degradarea progresiva a
performantei sau caderea/blocarea brusca a unui sistem software, cauzata de
epuizarea resurselor sistemului de operare, fragmentare si acumulare de erori.
In mod tipic in C++ o scurgere de memorie apare atunci cand o zona de
memorie alocata dinamic devine inaccesibila, deoarece pointerii care
retineau adresa ei au fost suprascrisi sau distrusi.
Scurgerile de memorie sunt mai serioase atunci cand se aloca frecvent memorie
noua pentru scopuri singulare, cum ar fi randarea cadrelor (frame) unui
joc sau animatii.
Exemplu:
========
#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
char *upcase(char *s){
char *temp;
temp = (char *) malloc((strlen(s)+1) * sizeof(char));
strcpy(temp, s);
free(s);
for(int i = 0; i < strlen(temp); ++i)
if('a' <= temp[i] && temp[i] <= 'z')
temp[i] -= ' ';
// daca 'temp[i]' este litera mica, devine majuscula corespunzatoare
return temp;
}
int main(){
int i;
int *p, *q;
p = (int *) malloc(sizeof(int)); *p = 1;
cout << p << " " << *p << endl;
// afisaza adresa si continutul zonei: 0x9d7c008 1
q = (int *) malloc(sizeof(int)); *q = 2;
cout << q << " " << *q << endl;
// afisaza adresa si continutul zonei: 0x9d7c018 2
p = q;
// apare un memory leak, deoarece zona de la adresa 0x9d7c008
// a ramas alocata, dar nu mai poate fi accesata (de exemplu pentru a
// o dezaloca cu 'free') deoarece adresa ei nu mai este retinuta
// nicaieri;
// acum 'p' si 'q' pointeaza zona de la adresa 0x9d7c018
free(q);
// se elibereaza zona de la adresa 0x9d7c018; 'p' si 'q' raman dangling
char *x;

x = (char *) malloc(100 * sizeof(char));


cin.getline(x, 100);
// se citeste un sir in zona alocata, de exemplu: aB1
cout << upcase(x) << endl;
// afisaza sirul citit trecut la majuscule, de exemplu: AB1
// In acest moment zona pointata de 'x' a fost dezalocata in
// functia 'upcase' (iar 'x' este dangling) iar zona alocata in
// functia 'upcase' a ramas alocata, dar i s-a pierdut urma,
// deoarece 'temp' a fost distrus (memory leak)
char **v = NULL; int n = 0;
do{
char buf[100];
cout << "Introduceti urmatorul sir (ENTER pentru terminare):";
cin.getline(buf, 100);
if(strlen(buf) == 0) break;
v = (char **) realloc(v, ++n * sizeof(char *));
v[n - 1] = (char *) malloc((strlen(buf)+1) * sizeof(char));
strcpy(v[n - 1], buf);
} while(1);
cout << "Am citit:\n";
for(i = 0; i < n; ++i) cout << v[i] << endl;
free(v);
// se dezaloca zona de n * sizeof(char *) pointata de 'v', dar nu se
// dezaloca automat si zonele pointate de v[0], ..., v[n-1] (memory
// leak);
// o abordare corecta ar fi fost ca in loc de:
//
//
free(v);
//
//
sa scriem:
//
//
for(i = 0; i < n; ++i) free(v[i]);
//
free(v);
class linie{
char *l;
public:
linie(char const *p)
{l = new char [(strlen(p) +1) * sizeof(char)]; strcpy(l, p);}
~linie()
{delete(l);}
operator char const * ()
{return l;}
};
class text{
linie **t; int n;
public:
text() {t = NULL; n = 0;}
~text() {
for(int i = 0; i < n; ++i) delete t[i];
free(t);
}
text& operator<< (char const *p) {
t = (linie **) realloc(t, ++n * sizeof(linie *));
t[n - 1] = new linie(p);
return *this;
}
char const * operator[] (int i) {return *t[i];}
} *w;

w = new text;
do{
char buf[100];
cout << "Introduceti urmatorul sir (ENTER pentru terminare):";
cin.getline(buf, 100);
if(strlen(buf) == 0) break;
*w << buf;
} while(1);
cout << "Am citit:\n";
for(i = 0; i < n; ++i) cout << (*w)[i] << endl;
delete w;
// o varianta obiectuala a codului anterior; apelurile 'delete' pe care
// le executa destructorii apelati fac ca sa nu mai avem memory leak;
// pentru membrul 't' am folosit 'realloc' in loc de 'new' deoarece
// codul pentru prelungirea zonei pointate se scrie mai usor;
// in scrierea 'cout << (*w)[i] << endl' parantezele '()' sunt necesare
// deoarece '*' are prioritate mai mare decat '[]';
{
text w1;
w1 << "abc" << "defg" << "h";
cout << w1[0] << endl << w1[1] << endl << w1[2] << endl;
// afisaza:
//
abc
//
defg
//
h
{text w2;
w2 = w1;
w2 << "ijk" << "lm";
cout << w2[0] << endl << w2[1] << endl << w2[2] << endl
<< w2[3] << endl << w2[4] << endl;
// afisaza:
//
abc
//
defg
//
h
//
ijk
//
lm
}
cout << w1[0] << endl << w1[1] << endl << w1[2] << endl
<< w1[3] << endl << w1[4] << endl;
// efect imprevizibil; de exemplu poate afisa tot:
//
abc
//
defg
//
h
//
ijk
//
lm
// sau programul se poate termina anormal;
}
// daca mai devreme programul nu s-a terminat anormal si se ajunge aici,
// iarasi efectul este imprevizibil;
// intr-adevar, clasa 'text' are operator '=' prin copiere implicit,
// deci la 'w2 = w1' se copiaza bit cu bit atributul 't' al lui 'w1'
// (adica adresa zonei dinamice pointate de 't') in cel al lui 'w2',
// fara a se duplica zona pointata de 't', a.i. in final 'w1' si 'w2'
// pointeaza acelasi vector de linii si aceleasi linii; de aceea, cand
// am modificat 'w2' (prin adaugare de linii) s-a modificat si 'w1', iar
// cand s-au dezalocat zonele dinamice asociate lui 'w2' prin apelarea
// destructorului (la iesirea din domeniul sau de existenta, care este
// instructiunea compusa interioara) s-au dezalocat si cele asociate lui
// 'w1'; deci, cand am incercat sa afisam din nou 'w1' s-a incercat

// accesarea unor zone dezalocate, iar cand s-a apelat destructorul


// pentru 'w1' (la iesirea din domeniul sau de existenta, care este
// instructiunea compusa exterioara) s-a incercat dezalocarea unor zone
// dezalocate, ambele incercari avand efect imprevizibil;
// daca la apelarea constructorului pentru 'w1' (la intalnirea definitiei
// sale de la inceputul instructiunii compuse exterioare) s-ar fi alocat
// memorie (in loc sa se faca 't = NULL'), in urma atribuirii 'w2 = w1'
// s-ar fi pierdut accesul la aceasta memorie (memory leak); intrucat
// la iesirea din instructiunea compusa interioara obiectul 'w1' ajunge
// sa pointeze zone dezalocate, avem de-a face si cu fenomenul de
// dangling pointer;
// pentru a evita atat memory leak-urile cat si dangling pointerii care
// apar mai sus, ar trebui ca atat clasa 'text' cat si clasa 'linie',
// ale caror atribute pointeaza zone dinamice, sa aiba operator '='
// de copiere definit de utilizator care sa duplice aceste zone
// (destructori definiti de utilizator care dezaloca corect aceste zone
// au deja);
}
Pentru a evita fenomenul de memory leak, o regula de urmat este sa ne
asiguam ca la orice rulare a programului, pentru fiecare 'malloc'/'calloc'
executat se va executa si cate un 'free', si pentru fiecare 'new'/'new []'
executat se va executa si cate un 'delete'/'delete[]'; un 'realloc'
redimensioneaza sau inlocuieste o zona alocata cu 'malloc'/'calloc', nu
creste numarul zonelor curent alocate, deci lui nu trebuie sa-i corespunda
un 'free' (decat daca eeste un 'realloc(NULL, ...)', care are efectul lui
'malloc').
O alta recomandare este: ce s-a alocat cu 'malloc'/'calloc'/'realloc' sa se
dezaloce cu 'free' (nu cu 'delete'), iar ce s-a alocat cu 'new' sa se
dezaloce cu 'delete' (nu cu 'free'); o explicatie ar fi ca tabelele interne
folosite de 'malloc'/'calloc'/'realloc' si 'free', respectiv 'new'/'delete',
pentru a retine adresele zonelor alocate si dimensiunile lor nu sunt neaparat
aceleasi.
Exemplul urmator ilustreaza fenomene legate de crearea vectorilor de obiecte,
independenti sau membrii ai unor obiecte, cu diverse clase de alocare
(statici, automatici, dinamici), evidentiind ordinea in care sunt considerate
componentele pentru a li se aplica constructorii/destructorii:
Exemplu:
========
#include<iostream>
using namespace std;
class cls{
static int gen; int n;
public:
cls();
~cls();
};
int cls::gen = 0;
cls::cls() {n = ++gen; cout << "Ctor cls: " << n << " " << this << endl;}
cls::~cls() {cout << "Dtor cls: " << n << " " << this << endl;}
class cls1{
static int gen; int n;
cls va[2];
public:

cls1();
~cls1();
};
int cls1::gen = 0;
cls1::cls1()
{n = gen +=100; cout << "Ctor cls1: " << n << " " << this << endl;}
cls1::~cls1() {cout << "Dtor cls1: " << n << " " << this << endl;}
cls vg[2]; // vector static de obiecte
int main() {
cout << "--- 1 ---\n";
static cls1 og; // obiect static (local) cu atribut vector de obiecte
cout << "--- 2 ---\n";
{
cls vl[2]; // vector automatic de obiecte
// (local unei instructiuni compuse)
cout << "--- 3 ---\n";
cls1 ol;
// obiect automatic cu atribut vector de obiecte
// (local unei instructiuni compuse)
cout << "--- 4 ---\n";
}
cout << "--- 5 ---\n";
cls *p = new cls[2]; // vector dinamic de obiecte
delete []p;
cout << "--- 6 ---\n";
cls1 *q = new cls1[2]; // vector dinamic de obiecte cu atribut vector
// de obiecte
delete []q;
cout << "--- 7 ---\n";
}
Comentariu: programul afisaza:
Ctor cls: 1 0x804b100
Ctor cls: 2 0x804b104
--- 1 --Ctor cls: 3 0x804b11c
Ctor cls: 4 0x804b120
Ctor cls1: 100 0x804b118
--- 2 --Ctor cls: 5 0xbf883610
Ctor cls: 6 0xbf883614
--- 3 --Ctor cls: 7 0xbf883608
Ctor cls: 8 0xbf88360c
Ctor cls1: 200 0xbf883604
--- 4 --Dtor cls1: 200 0xbf883604
Dtor cls: 8 0xbf88360c
Dtor cls: 7 0xbf883608
Dtor cls: 6 0xbf883614
Dtor cls: 5 0xbf883610
--- 5 --Ctor cls: 9 0x84b500c
Ctor cls: 10 0x84b5010
Dtor cls: 10 0x84b5010
Dtor cls: 9 0x84b500c
--- 6 --Ctor cls: 11 0x84b5020

Ctor cls: 12 0x84b5024


Ctor cls1: 300 0x84b501c
Ctor cls: 13 0x84b502c
Ctor cls: 14 0x84b5030
Ctor cls1: 400 0x84b5028
Dtor cls1: 400 0x84b5028
Dtor cls: 14 0x84b5030
Dtor cls: 13 0x84b502c
Dtor cls1: 300 0x84b501c
Dtor cls: 12 0x84b5024
Dtor cls: 11 0x84b5020
--- 7 --Dtor cls1: 100 0x804b118
Dtor cls: 4 0x804b120
Dtor cls: 3 0x804b11c
Dtor cls: 2 0x804b104
Dtor cls: 1 0x804b100
deci, in toate cazurile, componentele vectorilor s-au creat in ordinea
crescatoare a adreselor (indicilor) si s-au distrus in ordinea inversa
crearii; momentul crearii/distrugerii a fost de fiecare data in conformitate
cu clasa de alocare (storage class), a se vedea regulile de mai sus.
Constructorii, ca si alte functii, pot avea parametri impliciti (i.e. cu
valori implicite); alegerea lor de catre compilator, ca si ambiguitatile care
pot aparea, se supun acelorasi reguli valabile pentru functii in general.
Utilizarea constructorilor cu parametri impliciti permite reducerea
numarului de constructori.
Exemplu:
========
#include<iostream>
using namespace std;
class cls{
int a, b, c;
public:
cls();
cls(int);
cls(int, int);
cls(int, int, int);
void show();
};
cls::cls() {a = 0; b = 0; c = 0;}
cls::cls(int x) {a = x; b = 0; c =
cls::cls(int x, int y) {a = x; b =
cls::cls(int x, int y, int z) {a =
void cls::show() {cout << a << " "

0;}
y; c = 0;}
x; b = y; c = z;}
<< b << " " << c << endl;}

class cls1{
int a, b, c;
public:
cls1(int = 0, int = 0, int = 0);
void show();
};
cls1::cls1(int x, int y, int z) {a = x; b = y; c = z;}
void cls1::show() {cout << a << " " << b << " " << c << endl;}
int main(){

cls x1, x2(1), x3(1, 2), x4(1, 2, 3);


cls1 y1, y2(1), y3(1, 2), y4(1, 2, 3);
x1.show(); x2.show(); x3.show(); x4.show();
cout << "---\n";
y1.show(); y2.show(); y3.show(); y4.show();
}
Comentariu: programul afisaza:
0 0
1 0
1 2
1 2
--0 0
1 0
1 2
1 2

0
0
0
3
0
0
0
3

deci, 'cls1' a reusit cu un singur constructor (aplicat la toate cele patru


obiecte 'y1', 'y2', 'y3', 'y4') sa obtina aceleasi initializari pe care le-a
obtinut 'cls' cu patru constructori diferiti (aplicati fiecare cate unuia
dintre cele patru obiecte 'x1', 'x2', 'x3', 'x4').
Exemplu:
========
#include<iostream>
using namespace std;
class cls{
public:
cls(int, int = 0);
cls(int, int, double = 0);
cls(int, int, double, int);
cls(int, int, int, double);
};
cls::cls(int x, int y) {cout << "Ctor 1\n";}
cls::cls(int x, int y, double z) {cout << "Ctor 2\n";}
cls::cls(int x, int y, double z, int t) {cout << "Ctor 3\n";}
cls::cls(int x, int y, int z, double t) {cout << "Ctor 4\n";}
int main(){
cls x1(1);
// afisaza: Ctor 1
cls x2(1, 2);
// eroare de ambiguitate intre:
// 'cls::cls(int, int, double)' si 'cls::cls(int, int)'
// intr-adevar, din forma apelului nu rezulta daca dorim apelarea
// constructorului cu doi parametri sau a celui cu trei parametri,
// al treilea parametru avand valoarea implicita
cls x3(1, 2, 3);
// afisaza: Ctor 2
cls x4(1, 2, 3, 4);
// eroare de ambiguitate intre:
// 'cls::cls(int, int, int, double)'

// si 'cls::cls(int, int, double, int)'


// intr-adevar, in ambele cazuri, intre tipurile parametrilor actuali,
// respectiv formali, avem o potrivire pe baza unei conversii
// predefinite fara pierdere de informatii (de la 'int' la 'double');
cls x5(1, 2, 3.0, 4);
// afisaza: Ctor 3
// (s-a identificat o potrivire exacta intre tipurile parametrilor
// respectiv formali);
cls x6(1, 2, 3, 4.0);
// afisaza: Ctor 4
// (s-a identificat o potrivire exacta intre tipurile parametrilor
// respectiv formali);
cls x7(1, 2, 3.0, 4.0);
// eroare de ambiguitate intre:
// 'cls::cls(int, int, int, double)'
// si 'cls::cls(int, int, double, int)'
// intr-adevar, in ambele cazuri, intre tipurile parametrilor actuali,
// respectiv formali, avem o potrivire pe baza unei conversii
// predefinite cu pierdere de informatii (de la 'double' la 'int');
}
Asa cum vom vedea in detaliu mai incolo, anumite metode ale claselor sunt
"speciale" (special member functions), in sensul ca compilatorul genereaza
automat niste versiuni ale lor daca sunt folosite, dar nu sunt definite
explicit de utilizator. Printre acestea se numara constructorul implicit,
constructorul de copiere si destructorul:
- Un constructor implicit este un constructor ce se poate apela fara
parametri actuali (fie pentru ca are lista vida de parametri, fie pentru ca
are toti parametrii impliciti).
Un constructor implicit se apeleaza de exemplu la crearea obiectelor
prin definitii fara parametri actuali, de exemplu 'clasa obiect;', sau
la crearea vectorilor de obiecte.
Daca pentru o clasa utilizatorul nu a definit nici un constructor,
compilatorul genereaza automat un constructor implicit cu lista vida de
parametri; daca insa utilizatorul a definit constructori, compilatorul nu-l
mai adauga si pe cel implicit generat automat, a.i. clasa nu va avea
constructori impliciti decat daca i-a definit utilizatorul.
- Un constructor de copiere este un constructor al carui prim parametru este
referinta la clasa careia ii apartine constructorul (ca zona 'const',
'volatile', ambele sau niciuna), iar urmatorii parametri, daca exista, sunt
toti impliciti.
Un constructor de copiere se apeleaza de exemplu la definirea obiectelor cu
initializare prin copierea altui obiect al aceleiasi clase, de exemplu
'clasa obiect1 = obiect2;'.
Daca pentru o clasa 'cls' utilizatorul nu a definit nici un constructor
de copiere, compilatorul genereaza automat unul, numit constructor de
copiere implicit, avand signatura 'cls(cls const &)' si care copiaza membru
cu membru (memberwise) atributele nestatice ale obiectului dat ca parametru
in cele ale obiectului curent; pentru atributele nestatice non obiect se
foloseste copierea bit cu bit, iar pentru cele obiect se foloseste
constructorul lor propriu de copiere, care poate fi cel implicit sau unul
definit de utilizator. In C++ 11 exista o nuantare a acestei reguli, ca
urmare a introducerii notiunilor de semantica de mutare, constructori de
mutare, operatori de atribuire prin mutare, referinte la rvalue (a se vedea
capitolele *** si *** din aceasta lectie).
Ca si in cazul operatorului de atribuire prin copiere implicit, modul de
lucru al constructorului de copiere implicit este satisfacator doar daca

toata informatia proprie unui obiect se afla in locatia sa; altfel, trebuie
folosit un constructor de copiere definit de utilizator, care sa faca o
'copiere in profunzime' (deep copy).
- Destructorul a fost deja discutat.
Daca pentru o clasa utilizatorul nu a definit un destructor, compilatorul
genereaza automat unul; acesta insa nu distruge in profunzime structura de
date definita de utilizator asociata obiectului respectiv - de exemplu daca
o parte din informatia proprie obiectului se afla intr-o zona alocata
dinamic si doar pointata din locatia obiectului, aceasta zona nu este
dezalocata, putand aparea memory leak - in asemenea situatii trebuie
folosit un destructor definit de utilizator.
Daca o clasa mosteneste alte clase si/sau are atribute nestatice obiect,
fiecare constructor al sau (definit explicit de utilizator sau generat
automat de compilator) apeleaza automat cate un constructor din fiecare
clasa de baza (pentru a initializa partea mostenita de acolo) si cate un
constructor pentru fiecare atribut nestatic obiect; constructorii apelati pot
fi si ei definiti de utilizator sau generati automat de compilator; similar,
destructorul (fie el definit de utilizator sau generat automat de compilator)
apeleaza automat destructorii (definiti de utilizator sau generati automat de
compilator) ai claselor de baza si ai atributelor nestatice obiect.
Selectia constructorilor apelati pentru clasele de baza si atributele
nestatice obiect poate fi una implicita sau definita de utilizator.
Constructorul implicit generat de compilator apeleaza constructorii
impliciti (definiti de utilizator sau generati automat) ai claselor de
baza si ai atributelor nestatice obiect. Constructorul de copiere generat de
compilator apeleaza constructorii de copiere (definiti de utilizator sau
generati automat) ai claselor de baza si ai atributelor nestatice obiect.
Constructorii definiti de utilizator permit specificarea constructorilor
apelati pentru clasele de baza si atributele nestatice obiect prin lista de
initializare; mai exact, un constructor poate fi definit fara lista de
initializare:
<Nume clasa>::<Nume clasa> (<Parametri formali>)
{<corp>}
sau cu LISTA DE INITIALIZARE, situata intre antet si corp si care consta
din ':' urmat de o succesiune de INITIALIZATORI, separati prin ',':
<Nume clasa>::<Nume clasa> (<Parametri formali>):
<Initializator 1>, ...,<Initializator n>
{<corp>}
unde <Initializator i> poate fi de forma:
<Nume clasa de baza> (<Expresie 1>,...,<Expresie n>)
sau:
<Nume atribut nestatic obiect> (<Expresie 1>,...,<Expresie n>)
sau:
<Nume atribut nestatic non obiect> (<Expresie>)
atunci, la apelarea acestui constructor, pentru fiecare clasa de baza
sau atribut nestatic obiect mentionat in lista se va apela un constructor
(definit de utilizator sau generat automat) avand n parametri, compatibili ca
tip cu <Expresie 1>,...,<Expresie n>, iar fiecare atribut nestatic non obiect

mentionat in lista se va initializa cu valoarea lui <Expresie>; in scrierea


'(<Expresie 1>,...,<Expresie n>)' n poate fi 0 (adica sa nu avem expresii),
dar trebuie sa fie prezente '()'; pentru clasele de baza si atributele
nestatice obiect nementionate in lista se va folosi constructorul implicit
(definit de utilizator sau generat automat); atributele nestatice non obiect
nementionate in lista vor avea o valoare implicita in concordanta cu clasa de
alocare a obiectului creat.
Constatam astfel ca un constructor de copiere definit de utilizator va apela
pentru clasele de baza si atributele nestatice obiect nementionate in lista
de initializare un constructor implicit, nu un constructor de copiere, cum ar
face constructorul de copiere implicit.
Putem trata unitar lucrurile, afirmand ca orice constructor (definit de
utilizator sau generat automat) are initializatori pentru toate clasele de
baza si toate atributele nestatice; unii initializatori sunt scrisi explicit,
altii sunt presupusi implicit. Un initializator absent din lista de
initializare a constructorului de copiere implicit (aici toti initializatorii
sunt absenti) se presupune ca invoca apelul unui constructor de copiere sau
copierea valorii bit cu bit. Un initializator absent din lista de
initializare a oricarui alt tip de constructor se presupune ca invoca apelul
unui constructor implicit sau initializarea cu o valoare implicita conform
clasei de alocare.
Notam ca atributele nestatice imutabile (i.e. ale caror stare/valoare nu mai
poate fi modificata dupa ce au fost create) nu pot fi initializate decat prin
prin initializatori ca mai sus (scrisi explicit sau presupusi implicit). De
ceea, daca clasa are atribute nestatice imutabile pentru care nu exista
constructor/valoare implicite, cum sunt atributele non obiect 'const' sau
referinte, ea nu se poate instantia decat cu constructori de copiere
impliciti sau cu constructori definiti de utilizatori care sa aiba
initializatori pentru atributele respective. De exemplu nu se poate instantia
cu constructorul implicit generet automat sau cu constructori (inculsiv de
copiere) definiti de utilizatori care nu au initializatori pentru acele
atribute.
Ordinea in care constructorii/destructorul unei clase date apeleaza
constructorii/destructorii claselor de baza si ai atributelor nestatice
obiect (selectati dupa regulile de mai sus) este urmatoarea:
- In cazul constructorului: se apeleaza constructorii claselor
de baza, in ordinea declararii acestor clase la mostenire;
apoi se apeleaza constructorii atributelor nestatice obiect, in ordinea
declarariia acestor atribute in clasa; apoi se executa corpul
constructorului.
Observam ca nu conteaza ordinea in care au fost scrisi initializatorii in
lista de initializare.
- In cazul destructorului: intai se apeleaza corpul destructorului, apoi se
apeleaza destructorii atributelor nestatice obiect si ai claselor de baza
in ordinea inversa celei in care s-au apelat constructorii.
Exemplu:
========
#include<iostream>
using namespace std;
class clsbaz1{
int n;
public:
clsbaz1(int);
~clsbaz1();
};

clsbaz1::clsbaz1(int x): n(x) {cout << "Ctor baz1 " << n << endl;}
clsbaz1::~clsbaz1() {cout << "Dtor baz1 " << n << endl;}
class clsbaz2{
public:
clsbaz2();
~clsbaz2();
};
clsbaz2::clsbaz2() {cout << "Ctor baz2\n";}
clsbaz2::~clsbaz2() {cout << "Dtor baz2\n";}
class clsatr{
int n;
public:
clsatr(int = 1000);
~clsatr();
};
clsatr::clsatr(int x): n(x) {cout << "Ctor atr " << n << endl;}
clsatr::~clsatr() {cout << "Dtor atr " << n << endl;}
class cls: clsbaz1, clsbaz2{
clsatr a1;
int n;
clsatr a2;
public:
cls(int);
cls(int, int);
~cls();
};
cls::cls(int x):
a2(3), n(x), clsbaz2(), clsbaz1(4), a1(5)
{cout << "Ctor cls " << x << "," << n << endl;}
cls::cls(int x, int y):
a1(x), clsbaz1(y)
{cout << "Ctor cls " << x << "," << y << "," << n << endl;}
cls::~cls(){cout << "Dtor " << n << endl;}
int main(){
{cls ob1(10);}
cout << "---\n";
{cls ob2(100, 200);}
}
Comentarii:
- Obiectele 'ob1', 'ob2' sunt locale automatice in cadrul unor instructiuni
compuse incluse in 'main', a.i. 'ob2' se distruge inainte de a se crea
'ob1'; astfel, putem urmari mai usor ordinea apelarii constructorilor si
destructorilor.
- Programul afisaza:
Ctor
Ctor
Ctor
Ctor
Ctor
Dtor
Dtor
Dtor
Dtor
Dtor

baz1 4
baz2
atr 5
atr 3
cls 10,10
10
atr 3
atr 5
baz2
baz1 4

--Ctor
Ctor
Ctor
Ctor
Ctor
Dtor
Dtor
Dtor
Dtor
Dtor

baz1 200
baz2
atr 100
atr 1000
cls 100,200,-1218110981
-1218110981
atr 1000
atr 100
baz2
baz1 200

- Constatam ca ordinea apelarii constructorilor nu depinde de ordinea


scrierii initializatorilor in listele de initializare, ci doar de ordinea
declararii claselor la mostenire si de ordinea declararii atributelor in
clasa; mai exact, in fiecare caz se apeleaza in ordine constructorii pentru
'clsbaz1', 'clsbaz2', 'a1', apoi se initializeaza 'n', apoi se apeleaza
constructorul pentru 'a2'; dealtfel, la compilarea programului de mai sus,
g++ semnaleaza ca warning faptul ca initializarile se vor face in alta
ordine decat s-a scris in listele de initializare; destructorii se apeleaza
in ordinea inversa celei in care s-au apelat constructorii.
- In lista de initializare a celui de-al doilea constructor al lui 'cls',
care s-a apelat la crearea lui 'ob2', nu am scris initializatori pentru
toate clasele de baza si toate atributele nestatice; in cazul clasei de
baza 'clsbaz2' si a atributului nestatic obiect 'a2' (pentru care s-a omis
initializatorul), s-a folosit constructorul implicit; atributul nestatic
non obiect 'n' (pentru care s-a omis de asemenea initializatorul) a primit
o valoare implicita in concordanta cu clasa de alocare (automatica) a lui
'ob2'; notam ca puteam omite si initializatorul lui 'a1', dar nu si pe cel
al lui 'clsbaz1' (deoarece 'clsbaz1' nu are constructor implicit).
Asa cum am mai spus, pentru orice clasa, daca utilizatorul nu a definit
constructori, compilatorul genereaza automat unul implicit fara parametri,
iar daca utilizatorul a definit constructori, compilatorul nu-l mai adauga si
pe cel implicit generat automat, a.i. clasa nu va avea constructori impliciti
decat daca i-a definit utilizatorul. Astfel, in exemplul anterior clasele
'clsbaz2' si 'clsatr' au constructori impliciti (definiti de utilizator,
primul este fara parametri, al doilea are toti parametrii impliciti), clasele
'clsbaz1' si 'cls' nu au constructori impliciti.
De asemenea, daca pentru o clasa utilizatorul nu a definit un destructor,
compilatorul ii adauga unul generat automat (altfel, nu-l mai adauga). Toate
clasele din exemplul anterior au destructori definiti de utilizator.
Exemplul urmator ilustreaza modul cum pentru clasele de baza si atributele
nestatice obiect pentru care s-au omis initializatorii se apeleaza
constructorii impliciti, fie ei definiti de utilizator sau generati automat
de compilator, ca si anumite ambiguitati legate de selectarea constructorilor
prin initializatori; de asemenea, ilustreaza apelarea automata a
destructorilor, fie ei definiti de utilizator sau generati automat de
compilator:
Exemplu:
========
#include<iostream>
using namespace std;
class cls1{
public:
cls1();

~cls1();
};
cls1::cls1() {cout << "Ctor1\n";}
cls1::~cls1() {cout << "Dtor1\n";}
class cls2: cls1{};
// 'cls2' are constructor implicit si destructor generati automat
class cls3{
public:
cls3(int = 0);
cls3();
~cls3();
};
cls3::cls3(int x) {cout << "Ctor3 " << x << endl;}
cls3::cls3() {cout << "Ctor3 " << endl;}
cls3::~cls3() {cout << "Dtor3\n";}
class cls{
cls1 a; cls2 b; cls3 c;
public:
cls();
};
cls::cls():c(1) {cout << "Ctor cls\n";}
// 'cls' are destructor generat automat
int main(){
cls ob;
}
Comentarii:
- Programul afisaza:
Ctor1
Ctor1
Ctor3 1
Ctor cls
Dtor3
Dtor1
Dtor1
Intr-adevar, conform regulilor prezentate mai sus, la crearea lui 'ob'
mai intai se instantiaza atributul 'a', apoi atributul 'b', apoi atributul
'c', apoi se executa corpul constructorului folosit din clasa 'cls'; el
trebuie sa fie un constructor implicit, deoarece definitia lui 'ob' nu
contine parametri actuali; in clasa 'cls' exista un singur constructor
implicit, iar acesta este definit de utilizator; in lista sa de initializare
exista initializator doar pentru 'c'; de aceea, pentru 'a' si 'b' se
foloseste un constructor implicit.
In cazul lui 'a', singurul constructor implicit al clasei 'cls1' este cel
definit de utilizator (intrucat clasa are constructori definiti de
utilizator, copilatorul nu ii mai adauga constructorul implicit generat
automat); acesta este apelat si se afisaza 'Ctor1'.
In cazul lui 'b', singurul constructor implicit al clasei 'cls2' este cel
generat automat de compilator (intrucat clasa nu are constructori definiti
de utilizator); acesta trebuie sa apeleze un constructor pentru clasa de
baza 'cls1' si intrucat nu are lista de initializare scrisa de utilizator,
va apela pentru 'cls1' un constructor implicit; singurul asemenea
constructor este, cum am vazut, cel definit de utilizator; acesta este
apelat si se afisaza (din nou) 'Ctor1'; constructorul implicit generat

automat pentru 'cls2' nu afisaza altceva in plus. Notam ca daca in locul


constructorului implicit generat automat pentru 'cls2' s-ar fi apelat
constructorul de copiere generat automat pentru 'cls2', acesta ar fi
apelat constructorul de copiere al clasei de baza 'cls1' (care este de
asemenea generat automat), si nu cel implicit.
In cazul lui 'c', se apeleaza constructorul cu un parametru, conform
initializatorului scris de utilizator, iar acesta afisaza 'Ctor3 1'.
In final, constructorul lui 'cls' afisaza 'Ctor cls'.
Destructorii se apeleaza in ordinea inversa constructorilor; observam cum
destructorul generat automat pentru 'cls' a apelat destructorii claselor
lui 'c', 'b', 'a', fie ei definiti de utilizator sau generati automat; de
asemenea, destructorul generat automat pentru 'cls2' a apelat destructorul
definit de utilizator pentru clasa sa de baza 'cls1'.
- Daca in lista de initializare a constructorului lui 'cls' ar fi lipsit
initializatorul lui 'c', compilatorul ar fi semnalat eroare de ambiguitate;
intr-adevar, el ar fi cautat un constructor implicit pentru 'cls3', iar
aceasta clasa are doi constructori impliciti.
Exemplul urmator ilustreaza fenomene legate de initializarea atributelor
nestatice imutabile (reamintim ca ele nu pot fi initializate decat prin
initializatori, scrisi explicit sau presupusi implicit, in listele de
initializare ale constructorilor):
Exemplu:
========
#include<iostream>
using namespace std;
int glob, buf[100];
class cls1{
public:
int const n; int &r;
};
// 'n' si 'r' nu accepta valori implicite;
// 'cls1' are constructor implicit si constructor de copiere generati
// automat;
// constructorul implicit nu se poate folosi, deoarece s-ar presupune ca
// invoca pentru 'n' si 'r' initializarea cu valori implicite;
// constructorul de copiere se poate folosi (daca avem pe cine copia),
// deoarece se presupune ca invoca pentru 'n' si 'r' copierea bit cu bit
// (ceea ce se accepta);
cls1 a1 = *(cls1*)buf;
// pentru 'a1' se foloseste constructorul de copiere, copiindu-se un obiect
// fictiv presupus a se afla la adresa 'buf';
class cls2{
public:
int const n; int &r;
cls2(): n(0), r(glob) {}
};
// 'n' si 'r' nu accepta valori implicite;
// 'cls2' are constructor implicit definit de utilizator si constructor de
// copiere generat automat; in definirea lui 'cls2()' initializatorii lui
// 'n' si 'r' nu pot fi omisi (deoarece s-ar presupune ca invoca
//
initializarea cu o valoare implicita);
// ambii constructori se pot folosi;
cls2 a2, b2 = a2;
// pentru 'a2' se foloseste constructorul implicit,

//

pentru 'b2' cel de copiere;

class cls3{
public:
int const n; int &r;
cls3(cls3 const &x): n(x.n), r(x.r){}
};
// 'n' si 'r' nu accepta valori implicite;
// 'cls3' nu are constructor implicit (deoarece are constructori definiti
// utilizator si nici unul nu este implicit) si are constructor de
// copiere definit de utilizator; in definirea lui 'cls3(cls3 const &)'
// initializatorii lui 'n' si 'r' nu pot fi omisi (deoarece s-ar
// presupune ca invoca tot initializarea cu o valoare implicita, nu
// copierea valorii bit cu bit, cum s-ar intampla in cazul cand
// constructorul de copiere al lui 'cls3' ar fi cel generat automat);
// acest constructor de copiere se poate folosi (daca avem pe cine copia);
cls3 a3 = *(cls3*)buf;
// pentru 'a3' se foloseste constructorul de copiere, copiindu-se un obiect
// fictiv presupus a se afla la adresa 'buf';
class cls4{
public:
int const n; int &r;
cls4(): n(0), r(glob) {}
cls4(cls4 const &x): n(x.n), r(x.r) {}
};
// 'n' si 'r' nu accepta valori implicite;
// 'cls4' are constructor implicit si constructor de copiere definiti de
// utilizator; in definirea acestora, initializatorii lui 'n' si 'r' nu
// pot fi omisi (deoarece s-ar presupune ca invoca initializarea cu o
// valoare implicita);
// ambii constructori se pot folosi;
cls4 a4, b4 = *(cls4*)buf;
// pentru 'a4' se foloseste constructorul implicit,
// pentru 'b4' cel de copiere;
class cls1a : public cls1{};
class cls1b : public cls1a {
public:
cls1b(): cls1a(*(cls1a*)buf) {}
cls1b(cls1b const &x): cls1a() {}
};
class cls_unu{
public:
cls1b c1;
};
// 'cls_unu' are constructor implicit si constructor de copiere generati
// automat;
// constructorul implicit al lui 'cls_unu'(generat automat) invoca (in
// absenta specificarii) constructorul implicit al atributului nestatic
// 'c1', care este un constructor definit de utilizator; acesta invoca
// (conform specificarii) constructorul de copiere al clasei de baza
// 'cls1a', care este un constructor generat automat; acesta invoca (in
// absenta specificarii) constructorul de copiere al clasei de baza
// 'cls1', care este un constructor generat automat si care invoca (in
// absenta specificarii) copierea bit cu bit a unor valori din zona
// aflata la adresa 'buf' in 'n' si 'r'; per total, constructorul
// implicit al lui 'cls_unu' se poate folosi;

// constructorul de copiere al lui 'cls_unu' (generat automat) invoca


// (in absenta specificarii) constructorul de copiere al atributului
// nestatic 'c1', care este un constructor definit de utilizator; acesta
// invoca (conform speficicarii) constructorul implicit al clasei de baza
// 'cls1a', care este un constructor generat automat; acesta invoca
// (in absenta specificarii) constructorul implicit al clasei de baza
// 'cls1', care este generat automat si care invoca (in absenta
// specificarii) initializarea lui 'n' si 'r' cu valoari implicite, ceea
// ce nu se accepta; per total, constructorul de copiere al lui 'cls_unu'
// nu ar trebui sa se poata folosi, dar g++ accepta instantierea acestei
// clase prin copiere;
// de notat ca daca omitem initializatorul 'cls1a()' din definitia
// constructorului 'cls1b(cls1b const &)', g++ semnaleaza eroare de
// compilare (desi in mod normal ar trebui sa se presupuna prin lipsa
// tot invocarea constructorului implicit al lui 'cls1a');
cls_unu ob;
// pentru 'ob' se foloseste constructorul implicit;
cls_unu ob1 = ob;
// pentru 'ob1' se foloseste constructorul de copiere
int main(){
cout << ob1.c1.n << endl; // afisaza: 0
}
TODO: alte exemple care sa justifice folosirea de constructori/destructori
definiti de utilizator (stive, arbori, stringuri, etc).
IV. Constructori impliciti
==========================
Un CONSTRUCTOR IMPLICIT (DEFAULT CONSTRUCTOR) este un constructor care
se poate apela fara parametri actuali (fie pentru ca are lista vida de
parametri, fie pentru ca are toti parametrii impliciti). Un tip cu un
constructor implicit public este CONSTRUCTIBIL IMPLICIT (DEFAULT
CONSTRUCTIBLE).
Constructorii impliciti sunt invocati automat in urmatoarele situatii:
- La intalnirea unei definitii de variabile obiect, statice sau automatice,
cu lista vida de parametri actuali:
<Nume clasa> <Nume obiect>;
similar, la intalnirea unei definitii de atribut static care este la randul
sau obiect, iar definitia are lista vida de parametri actuali:
<Nume clasa atribut> <Nume clasa>::<Nume atribut>;
in toate aceste cazuri, constructorul implicit este folosit pentru a
initializa obiectul creat.
- La intalnirea unei definitii de vector de obiecte, static sau automatic,
fara lista de clauze de initializare intre '{}':
<Nume clasa> <Nume vector de obiecte> [<Dimensiune>];
similar, la intalnirea unei definitii de atribut static care este la randul
sau vector de obiecte, ia definitia nu are lista de clauze de initializare
intre '{}':

<Nume clasa atribut> <Nume clasa>::<Nume atribut> [<Dimensiune>];


in toate aceste cazuri, constructorul implicit este folosit pentru a
initializa toate componentele vectorului.
- La intalnirea unei cereri de alocare dinamica a unui obiect,
cu operatorul 'new', fara parametri actuali (sau cu lista vida de parametri
actuali, intre '()'):
new <Nume clasa>
new <Nume clasa> ()
in toate aceste cazuri, constructorul implicit este folosit pentru a
initializa obiectul creat.
- La intalnirea unei cereri de alocare dinamica a unui vector de obiecte,
cu operatorul 'new':
new <Nume clasa> [<Dimensiune>]
new <Nume clasa> [<Dimensiune>] ()
in toate aceste cazuri, constructorul implicit este folosit pentru a
initializa toate componentele vectorului.
- La crearea unui obiect temporar printr-un apel explicit al constructorului
implicit:
<Nume clasa> ()
- La apelarea unui constructor definit de utilizator al unei clase derivate
care nu are in lista sa de initializare initializatori pentru anumite clase
de baza - atunci, pentru clasele de baza respective se invoca constructorul
implicit.
- La apelarea unui constructor definit de utilizator al unei clase care nu
are in lista sa de initializare initializatori pentru toate atributele
nestatice obiect ale clasei respective - atunci, pentru atributele
respective se peleaza constructorul implicit.
- Constructorul implicit generat de compilator pentru o clasa (a se vedea
mai jos) invoca constructorii impliciti ai claselor de baza si ai
atributelor nestatice obiect ale clasei respective (cel de copiere generat
automat invoca constructorii de copiere).
- In biblioteca standard de sabloane STL, anumite containere "umplu" cu
valori folosind constructorul implicit atunci cand valoarea nu este data
explicit, de exemplu:
vector <ClasaMea> (10);
initializeaza vectorul cu 10 componente, care sunt umplute cu valoarea
construita cu constructorul implicit al lui 'ClasaMea'; pentru alte
detalii, a se vedea lectia ***.
In toate situatiile de mai sus, daca clasa vizata nu nici un constructor
implicit se semnaleaza eroare, iar daca are mai multi constructori impliciti
(de exemplu unii care difera prin lista tipurilor parametrilor, ca sa fie
permisa supraincarcarea, dar au toti parametrii impliciti), se semnaleaza de
asemenea eroare (de ambiguitate).

Daca pentru o clasa (in particular tip structura, uniune) utilizatorul nu a


definit nici un constructor, compilatorul genereaza automat un constructor
implicit; acest constructor este declarat implicit daca clasa necesita un
constructor si este definit implicit atunci cand compilatorul il foloseste
pentru a crea un obiect al clasei respective; el se mai numeste CONSTRUCTOR
IMPLICIT IMPLICIT (IMPLICIT DEFAULT CONSTRUCTOR, sau IMPLICITLY-DECLARED
DEFAULT CONSTRUCTOR, sau DEFAULTED DEFAULT CONSTRUCTOR).
Daca insa pentru clasa respectiva utilizatorul a definit constructori,
compilatorul nu-l mai adauga si pe cel implicit generat automat, a.i. clasa
nu va avea constructori impliciti decat daca i-a definit utilizatorul.
Constructorul implicit generat automat este echivalent cu un constructor
implicit definit de utilizator avand corp vid; in plus, este inline, public,
are lista de parametri si lista de initializare vide.
Exemplu:
========
#include<iostream>
using namespace std;
class cls1{};
// 'cls1' are constructor implicit generat automat
class cls2{
public:
cls2(int);
cls2();
};
cls2::cls2(int x) {cout << "cls2(int)\n";}
cls2::cls2() {cout << "cls2()\n";}
// 'cls2' are constructor implicit definit de utilizator
class cls3{
public:
cls3(int);
};
cls3::cls3(int x) {cout << "cls3(int)\n";}
// 'cls3' nu are constructori impliciti
class cls_unu: cls1, cls2, cls3{
cls1 a; cls2 b; cls3 c;
public:
cls_unu();
cls_unu(int);
};
cls_unu::cls_unu():
cls1(),
cls3(1),
b(), c(2)
{cout << "cls_unu()\n";}
/*
constructor implicit definit de utilizator; el apeleaza in ordine
constructori pentru clasele:
'cls1', ca prima clasa de baza;
'cls2', ca a doua clasa de baza;
'cls3', ca a treia clasa de baza;
'cls1', ca fiind clasa primului atribut obiect;
'cls2', ca fiind clasa celui de-al doilea atribut obiect;
'cls3', ca fiind clasa celui de-al treilea atribut obiect;
lipsa initializatorilor sau initializatorii cu lista vida de parametri

indica faptul ca pentru 'cls1' si 'cls2' vor fi apelati constructori


impliciti; pentru 'cls1' este cel generat automat, pentru 'cls2' este cel
definit de utilizator; initializatorii 'cls3(1)' si 'c(2)' indica faptul ca
pentru 'cls3' va fi apelat constructorul cu un parametru; initializatorii
pentru clasa de baza 'cls3' si pentru atributul 'c' nu pot fi omisi,
deoarece 'cls3' nu are constructori impliciti;
*/
cls_unu::cls_unu(int x): cls3(10),c(20) {cout << "cls_unu(" << x << ")\n";}
/*
constructor cu un parametru;
comentariile sunt la fel ca in cazul lui 'cls_unu()'
*/
class cls_doi: cls1, cls2, cls3{
cls1 a; cls2 b; cls3 c;
} ob;
/*
eroare la compilare; intr-adevar, 'cls_doi' ar trebui sa aiba un
constructor implicit generat automat, acesta nu are lista de initializare,
deci nu are initializatori pentru clasa de baza 'cls3' si pentru atributul
'c', ori am vazut mai sus ca acestia nu pot fi omisi, deoarece 'cls3' nu
are constructori impliciti; daca nu am fi instantiat clasa (definindu-l pe
'ob') nu s-ar fi semnalat eroare la compilare (constructorul implicit
generat automat este definit implicit doar atunci cand compilatorul il
foloseste pentru a crea un obiect al clasei);
*/
int main(){
cls2 x2, y2(10), z2[3], *p2 = new cls2[5];
/*
pentru 'x2', componentele lui 'z2' si componentele vectorului dinamic
pointat de 'p2' se foloseste constructorul implicit (definit de
utilizator) al lui 'cls2'; pentru 'y2', constructorul cu un parametru;
*/
cls1 x1, z1[3], *p1 = new cls1[5];
/*
pentru 'x1', componentele lui 'z1' si componentele vectorului dinamic
pointat de 'p1' se foloseste constructorul implicit (generat automat)
al lui 'cls1';
*/
cls3 x3, z3[3], *p3 = new cls3[5];
/*
erori la compilare, deoarece 'cls3' nu are constructori impliciti;
*/
}
In C++11 exista posibilitatea ca pentru o clasa definita de utilizator
'cls' sa se poata inhiba generarea automata a unui constructor implicit,
declarand in interiorul clasei:
cls() = delete;
sau sa se poata forta generarea automata a constructorului implicit,
declarand in interiorul clasei:
cls() = default;
In anumite situatii, constructorul implicit generat automat este nedefinit
(pana la C++11) sau definit ca sters (deleted) (incepand cu C++11), de
exemplu daca clasa are atribute referinta sau 'const' fara initializatori

acolada-sau-egal (brace-or-equal initializer); pentru alte detalii, a se


vedea:
http://en.cppreference.com/w/cpp/language/default_constructor
Constructorul implicit generat automat pentru o clasa 'cls' este TRIVIAL
daca sunt indeplinite toate conditiile urmatoare:
- 'cls' nu are metode virtuale;
- 'cls' nu are clase de baza virtuale;
- 'cls' nu are atribute nestatice cu initializatori acolada-sau-egal
(brace-or-equal initializer) (incepand cu C++11);
- orice clasa de baza directa a lui 'cls' are un constructor implicit
trivial;
- orice atribut nestatic obiect are un constructor implicit trivial.
Un constructor implicit trivial nu efectueaza nici o actiune. Obiectele cu
constructor implicit trivial pot fi create folosind 'reinterpret_cast'
aplicat oricarei zone de memorie convenabil aliniate, de exemplu asupra
zonelor de memorie alocate cu 'std::malloc'. Toate tipurile de date
compatibile cu limbajul C (tipurile POD) au constructor implicit trivial
public (i.e. sunt CONSTRUCTIBILE IMPLICIT TRIVIAL (TRIVIALLY
DEFAULT-CONSTRUCTIBLE)).
Exemplu:
========
#include<iostream>
using namespace std;
class cls1 {
int i;
public:
cls1() = delete;
};
// 'cls1' nu are constructor implicit generat automat, desi nu are
//
constructori definiti de utilizator (intrucat acesta a fost sters);
cls1 ob1; // eroare, clasa nu mai are constructor implicit
class cls2 {
int i;
public:
cls2(int x): i(x) {}
cls2() = default;
};
// 'cls2' are constructor implicit generat automat, chiar daca are
//
constructori definiti de utilizator (intrucat acesta a fost fortat);
cls2 ob2; // OK, se aplica constructorul implicit generat automat
class clsaux {
public:
int x;
};
class cls3: public clsaux {
public:
clsaux n;
};
// 'clsaux' si 'cls3' au constructor implicit trivial
int main() {
cls3 *ob = reinterpret_cast <cls3 *> (malloc(100));
ob -> x = 1; ob -> n.x = 2;