Sunteți pe pagina 1din 22

Supra ncrcarea operatorilor

When in doubt, do as the ints do! (Scott Meyers, More Effective C++) Limbajul C++ permite extinderea operatorilor astfel nct acetia s suporte i tipurile introduse de utilizator. Acest lucru se realizeaz prin definirea unor funcii al cror nume conine cuvntul cheie operator, urmat de identificatorul operatorului; o astfel de funcie, numit funcie operator, este declarat i poate fi apelat exact ca orice alt funcie. Deoarece o funcie operator trebuie s conin mcar un argument de un tip-utilizator (ceea ce garanteaz c sensul unei expresii formate doar din instane ale tipurilor predefinite nu poate fi modificat), ea suprancarc (i nu redefinete) semnificaia unui operator.

1. Caracteristicile funciilor operator


O funcie operator poate fi o funcie membru ne-static (cu excepia new i delete) a unei clase sau o funcie global (friend sau nu). Un operator poate fi suprancrcat pentru un tip simultan prin funcii membru i funcii globale; ntr-o astfel de situaie rezoluia suprancrcrii determin ce versiune a funciei operator va fi aleas la apel. n cazul unei funcii operator globale, mcar un argument trebuie s fie de un tip-utilizator. n particular, nu se pot defini funcii operator care s acioneze exclusiv pe pointeri. Cu alte cuvinte, limbajul C++ este extensibil, dar nu modificabil. Spre deosebire de funciile obinuite, funciile operator (cu excepia lui operator()()) nu pot avea valori implicite pentru argumente. Dac sunt funcii membru, funciile operator sunt motenite n acelai mod n care sunt motenite funciile membru ale unei clase de baz; excepie face operator=(), aceast funcie fiind implicit declarat pentru orice clas, ceea ce face ca varianta din clasa de baz s fie ntotdeauna ascuns de varianta din clasa derivat!

Numrul de argumente i precedena unui operator nu pot fi modificate prin suprancrcare; de asemenea, nu se pot introduce operatori noi! nelesul predefinit al operatorilor nu este pstrat de compilator pentru funciile operator introduse de utilizator. De exemplu, pentru x de tip int, expresia ++x este echivalent cu expresia x += 1 i cu expresia x = x + 1. Pentru un tip utilizator Y, aceste relaii nu vor avea loc dect dac utilizatorul are grij s le defineasc corespunztor; compilatorul nu va genera o definiie a lui Y::operator+=() din definiiile lui Y::operator+() i Y::operator=(), chiar dac acest lucru este posibil! Un prim motivele care justific aceast decizie se refer la eficien: versiunea creat automat a lui += ar utiliza obligatoriu un obiect temporar i deci ar putea fi cu mult mai puin eficient dect o versiune definit explicit de utilizator, care ar putea s evite temporarul. Un al doilea motiv ar fi faptul c utilizatorul ar putea s nu doreasc s-i doteze clasa cu operatorul +=!

2. Funcie membru sau funcie global?


Un operator binar x@y poate fi suprancrcat fie prin funcii membru cu un singur argument (x.operator@(y)), fie prin funcii globale cu dou argumente (operator@(x, y)). Rezoluia suprancrcrii determin care versiune va fi selectat:
//exemplul 1 class X{ public: void operator + (int); X(int); }; void operator + (X, X); void operator + (X, double); ... X a; a+1; //a.operator+(1) a+1; //::operator+(X(1), a) a+1.0; //::operator+(a, 1.0)

Un operator unar @x (sau x@) poate fi suprancrcat fie prin funcii membru fr argumente (x.operator@()), fie prin funcii globale globale cu un argument (operator@(x)). Deoarece ++ i -- sunt singurii operatori unari care pot aprea att n form pre-fixat ct i n form post-fixat, un argument suplimentar, de tip int, este utilizat n cazul lor pentru a face distincia ntre cele dou forme.
2

n general, se recomand suprancrcarea operatorilor prin funcii membru; suprancrcarea prin funcii globale s se utilizeze doar atunci cnd este necesar! Cnd este necesar? Cnd trebuie s se asigure simetria, comutativitatea sau cnd se dorete utilizarea conversiilor implicite!
//exemplul 2 class X{ public: void operator + (X); bool operator == (const X&) const; X(int); f(); }; X a; //+ este comutativ; a+1 i 1+a sunt echivalente; i totui: a + 1; 1 + a; // OK, a.operator+(X(1)) // eroare la compilare: nu exist int::operator + (X) i nici ::operator + (int, X)!

De ce compilatorul nu evalueaz expresia 1+a prin 1.operator+(a) i deci prin X(1).operator+(a)? Din acelai motiv pentru care expresia 1.f() nu este evaluat prin X(1).f(): standardul C++ interzice conversiile implicite definite de utilizator i aplicate operandului stng al lui . sau al lui ->! Acest lucru are dou consecie imediate. Prima este aceea c, dac primul operand al unui operator este de tip predefinit, atunci operatorul trebuie suprancrcat prin funcie global:
//dac operator + este suprancrcat prin funcia global void operator + (X, X); //atunci: a + 1; //OK, ::operator + (a, X(1)) 1 + a; //OK, ::operator + (X(1), a)

A doua consecin este faptul c, dac un operator impune ca primul operand s fie l-value, acel operator trebuie obligatoriu suprancrcat prin funcie membru. Este cazul operatorilor = (i variantele sale +=, - =, *=, /=, %=), (), [], -> i ->*, care se suprancarc ntotdeauna prin funcii membru. Faptul c operatorul == este suprancrcat prin funcie membru face ca el s fie asimetric. De ce? Pentru c primete argumente de tipuri diferite, pentru care nu exist definit nici o conversie standard: un pointer (de tipul const X* const this, deoarece este funcie membru const) i o referin (const X&). Pentru a se asigura simetria, este necesar ca operatorul s fie suprancrcat prin funcie global:
3

//bool operator==(const X&, const X&);

De ce ar fi necesar simetria? Algoritmii STL se bazeaz pe existena unor versiuni simetrice ale operatorului ==; de exemplu, un container STL de obiecte aparinnd unui tip care nu ndeplinete aceast cerin nu poate fi sortat printr-un algoritm STL!

3. Care operatori nu pot/nu se recomand a fi suprancrcai?


Urmtorii operatori nu pot fi suprancrcai: . (operatorul de acces direct la membru) .* (operatorul de derefereniere a unui pointer la membru) :: (operatorul de rezoluie a domeniului de vizibilitate) sizeof (operatorul de calcul a dimensiunii) typeid (operatorul de returnare a informaiei despre tip) ?: (operatorul condiional) static_cast<>, new i delete n cazul lui new i delete, nu se poate modifica ce fac aceti operatori, ci doar cum fac! Pentru mai multe detalii, consultai materialul dedicat acestor doi operatori. Operatorii &&, || i , se recomand a nu fi suprancrcai niciodat! Semantica de tip operator face ca C++ s scurt-circuiteze evaluarea expresiilor booleene: n momentul n care valoarea de adevr a unei expresii a fost stabilit, evaluarea expresiei nceteaz, chiar dac anumite pri din expresie nu au fost nc evaluate! Mai mult dect att, funcionarea corect a unor programe depinde de scurtcircuitarea evalurii:
//exemplul 3 char* p; ... if ((p != 0) && strlen(p) > 10) ...

const_cast<>,

reinterpret_cast<>,

dynamic_cast<>

(operatorii de conversie)

Deoarece invocarea lui strlen() asupra unui pointer NULL conduce la comportament nedefinit (conform standardelor C i C++), este foarte important ca, dac p == 0, partea a doua a expresiei s nu se evalueze! C++ permite suprancrcarea operatorilor && i ||; dac vei face acest lucru, regulile jocului se schimb pentru c vei utiliza pentru aceti operatori o semantic de tip funcie! De exemplu, e1&&e2 devine operator&&(e1, e2); n acest caz, deoarece parametrii unei funcii sunt ntotdeauna evaluai, scurt-circuitarea dispare! Mai mult dect att, cum ordinea de evaluare a parametrilor unei funcii este lsat nespecificat de ctre standard, este posibil ca e2 s se evalueze naintea lui e1, n direct contradicie cu semantica operator, n care argumentele se evalueaz ntotdeauna de la stnga spre dreapta. Aceeai problem apare i pentru , (operatorul de nlnuire a expresiilor) care-i evalueaz ntotdeauna nti operandul stng, apoi pe cel drept. Deoarece nu vei putea reui s facei aceti operatori s se comporte aa cum s-ar atepta utilizatorii, este recomandat s nu-i suprancrcai.

4. operator= ()
Deoarece operandul stng trebuie s fie l-value, operatorul de atribuire se suprancarc ntotdeauna prin funcie membru; mai mult dect att, compilatorul genereaz automat operator de atribuire pentru fiecare clas.
//exemplul 4 class X{ public: X(const char*); X(); }; X a,b,c; a = b = c = sir de caractere;

Deoarece operatorul de atribuire este drept-asociativ, compilatorul traduce atribuirea nlnuit din exemplul 4 prin a.operator= (b.operator= (c.operator= (X(sir de caractere)))). Se observ c operatorul de atribuire primete ca argument un obiect de acelai tip; din motive de eficien, acest obiect este transmis prin referin (se poate transmite i prin valoare, dar implic suplimentar un apel de constructor de copie i un apel de destructor). Deoarece atribuirea nu modific
5

argumentul, se utilizeaz referine const; se pot utiliza i referine obinuite, dar n acest caz apelurile de tip c.operator=(sir de caractere) nu ar mai reui. Aceasta deoarece, prin conversia implicit X(sir de caractere), este implicat suplimentar un obiect temporar; obiectele temporare sunt ntotdeauna const, i deci se pot apela doar metodele const, ceea ce, evident, operatorul de atribuire nu este! Deci tipul argumentului este const X&. Atribuirile nlnuite fac necesar ca tipul returnat de operatorul de atribuire s fie compatibil cu tipul recepionat; cum tipul recepionat este const X&, tipul returnat poate fi X& sau const X&. Din dorina de a mpiedica atribuirile de genul (a=b)=c, putei fi tentai s returnai const X&. S nu facei asta! De ce? Un prim motiv ar fi faptul c introducei o inconsisten n limbaj: dac a, b i c ar fi de tip int, expresia ar fi valid! Un al doilea motiv, mult mai serios, e acela c tipul X nu ar mai fi compatibil cu containerele STL, care cer explicit ca operatorul de atribuire s returnze referine ne-const! Aadar, tipul returnat este X&. Ajungem aadar la urmtorul prototip al operatorului de atribuire: X& X::operator= (const X& other); O alt ntrebare este: pe cine returnai, obiectul primit ca argument sau obiectul curent (*this)? Cum tipul recepionat este const X& iar tipul returnat este X&, ncercarea de a returna obiectul primit ca argument (other) ar genera eroare de compilare (se elimin const)! Se returneaz deci, ntotdeauna, referin la obiectul curent; cum operator=() este funcie membru, aceast referin va fi *this. Din motive de eficien trebuie ntotdeauna s v protejai mpotriva autoatribuirilor:
//exemplul 5 X a; a = a; X& b = a; ... a = b; //nu vei scrie aa ceva niciodat, nu? //suntei sigur?

Discuia e mai ampl, pentru c implic a stabili ce nsemn c dou obiecte sunt egale. De exemplu, dou instane s1 i s2 ale clasei std::string, care conin iruri de caractere identice, sunt egale sau nu? Dac rspunsul este da, atunci atribuirea s1= s2 nu ar trebui s fac nimic (o tehnic uzual de implementare a atribuirii ar consta n
6

eliberarea resursele din s1, alocarea de memorie i copierea irul de caractere din s2, ceea ce, n acest caz, ar fi ineficient)! A stabili ce nsemn c dou obiecte sunt egale depinde de problema modelat; deoarece fiecare obiect are o adres unic, un test de egalitate ntre this i &other este, adesea, suficient. Nu uitai c, pentru fiecare nou clas, compilatorul genereaz automat operatorul de atribuire; n cazul motenirii, aceasta are o importan deosebit pentru c varianta din clasa derivat nu gestioneaz i atribuirea ctre membrii provenii din clasa de baz! Pentru a extinde funcionalitatea operatorului de atribuire din clasa derivat se procedeaz astfel:
//exemplul 6 class Y : public X{ ... public: Y& operator= (const Y& other){ if (this == &other) return *this; X::operator= (other); ... return *this; } };

5. Nu ncercai s returnai prin referin cnd trebuie s returnai prin valoare!


S presupunem c, din motivele enumerate la 2, pentru un tip T suprancrcai operatorul binar + prin funcie global; n acest caz, expresia a+b devine operator+(a, b). Aceast funcie operator recepioneaz argumentele prin referin (se poate i prin valoare, dar nu ar fi la fel de eficient); cum a i b nu se modific n urma execuiei funciei operator, se utilizeaz referine const. Evident, suma a dou valori de tip T este o valoare de tip T, deci funcia operator trebuie s returneze o valoare de tip T; poate s o returneze prin referin (mai eficient)?
//exemplul 7 T& operator+ (const T& x, const T& y){ T aux; //calculeaz rezultatul n aux return aux; }

Deoarece aux este o variabil local, memoria alocat ei va fi dealocat la terminarea funciei operator i referina returnat va fi invalid! Putem ncerca s
7

evitm acest lucru garantnd existena locaiei de memorie a variabilei aux i n afara funciei operator:
//exemplul 8 T& operator+ (const T& x, const T& y){ static T aux; //calculeaz rezultatul n aux return aux; }

Pare ok; ns, n acest caz, o expresie de tipul (a+b)==(c+d) se va evalua ntotdeauna la true (deoarece se vor testa de egalitate dou referine ctre aceeai zon de memorie)! Ultima ncercare: salvm rezultatul ntr-o variabil alocat dinamic, a.. variabila exist i dup terminarea funciei operator iar apelurile succesive ale funciei operator plaseaz rezultatul n variabile distincte:
//exemplul 9 T& operator+ (const T& x, const T& y){ T* aux = new T; //calculeaz rezultatul n *aux return *aux; }

Se pare c ai reuit i meritai un premiu! Da, meritai cu adevrat un premiu dac vei reui s convingei un client s utilizeze acest operator n felul urmtor:
//exemplul 10 T a, b; T* p(&(a+b)); //utilizeaz *p delete p;

Cu alte cuvinte, trebuie s v convingei clientul ca, dup fiecare apelare a operatorului +, s elibereze zona de memorie n care este pstrat rezultatul! V urez succes! i dac clientul trebuie s scrie o expresie de forma a+b+c? Resemnai-v i acceptai c operatorul + are urmtorul prototip: const T operator+(const T&, const T&); Stai puin: const T? De ce const? Argumentul filosofic: suma a dou valori de tip T este o valoare de tip T, iar o valoare nu poate fi modificat! Argumentul practic: lipsa lui const ar da posibilitatea clientului s scrie expresia (a+b)=c, evident eronat d.p.d.v. logic. Cum

compilatorul refuz o astfel de expresie dac a, b i c ar fi de tip int, motto-ul articolului ne invit s respingem aceast expresie i pentru clasa noastr! N-ar trebui operator+() s fie funcie friend? Rspunsul, la seciunea 7!

6. Dotai-v clasele cu operatorii de atribuire compus!


tii din C c a=a+b este echivalent cu a+=b; de cte ori ai folosit a doua form? Dac rspunsul difer de ntotdeauna, atunci aflai c a doua form este mult mai eficient dect prima! Operatorul + este obligat s calculeze rezultatul ntr-o variabil local auxiliar i s-l returneze prin valoare. Operatorul += este suprancrcat obligatoriu prin funcie membru:
T& T::operator+=(const T& b){ //calculeaz rezultatul n *this return *this; }

Nu mai este nevoie de variabila auxiliar, cci rezultatul se calculeaz n obiectul curent; nu se mai returneaz prin valoare, ci se returneaz referin la obiectul curent! Q.E.D.

7. Ferete-m, Doamne, de prieteni...


Oferii unei funcii globale statutul de friend (adic acces la detaliile de implementare) doar dac acea funcie are, cu adevrat, nevoie de acest lucru. Are nevoie, de exemplu, operator+ s fie friend? Dac ai urmat sfatul de la punctul 6 atunci v-ai dotat clasa cu operatorul +=(); acesta este suprancrcat prin funcie membru i, evident, public; atunci urmtoarea definiie este suficient pentru operatorul +:
//exemplul 11 inline const T operator+ (const T& x, const T& y){ return T aux(x)+=y; }

8. ++ i - Aceti doi operatori sunt unici n C++, n sensul c ei pot fi utilizai att n form prefixat ct i n form postfixat. Un argument suplimentar, de tip int, este utilizat pentru a permite compilatorului s selecteze versiunea postfixat; versiunea prefixat nu are argumente. Mult mai important, cele dou versiuni returneaz tipuri diferite, iar versiunea prefixat este ntotdeauna mai eficient, aa cum se va vedea n continuare. O implementare uzual a pre-incrementrii poate arta astfel:
//exemplul 12 T& T::operator++(){ *this += 1; return *this; }

tii din C c, pentru a de tip int, expresia ++a nseamn incrementeaz-l pe a i returneaz noua valoare; cu alte cuvinte, operatorul returneaz valoarea calculat, adic *this. De asemenea, expresia ++++a este valid i reprezint dou incrementri succesive ale lui a; motto-ul articolului ne ndeamn s asigurm claselor noastre aceeai funcionalitate, ceea ce nsemn c operatorul de pre-incrementare trebuie s returneze *this prin referin! Tot din C tii c, pentru a de tip int, expresia a++ nsemn incrementeaz-l pe a i returneaz valoarea actual; aceasta nsemn c valoarea actual trebuie salvat mai nti ntr-o variabil auxiliar, pentru a putea fi returnat la sfrit, ceea ce conduca la necesitatea de a returna prin valoare! De asemenea, expresia a++++ este invalid i compilatorul semnaleaz eroare; asigurm aceeai funcionalitate claselor noastre declarnd const valoarea returnat. E chiar indicat s facem acest lucru pentru c, n mod evident, a++++ este o eroare logic i nu face ceea ce un utilizator mai puin experimentat ar crede c face. O implementare uzual a post-incrementrii poate arta astfel:
//exemplul 13 const T T::operator++(int){ T oldValue = *this; ++(*this); //pre-incrementare return oldValue; }

10

9. De ce << i >> sunt suprancrcai prin funcii globale?


Pare i este logic ca cei doi operatori s fie suprancrcai prin funcii membru:
//exemplul 14 class T{ public: ostream& operator << (ostream& output){} };

Apare ns imediat o problem de sintax: deoarece este funcie membru, operatorul este apelat prin intermediul unui obiect, o.operator<<(cout), ceea ce nsemn c ar trebui s scriem exact invers dect n mod obinuit:
//dac << este suprancrcat prin funcie membru T o; o << cout; //!!!!!

Aceeai problem apare i pentru >>; de aceea cei doi operatori se suprancarc prin funcii globale. Nu uitai s returnai fluxul primit ca argument pentru a permite afirile/citirile nlnuite! Ca de obicei, ferii-v de a declara cei doi operatori funcii friend. O tehnic uzual pentru a evita acest lucru const n dotarea clasei cu o funcie membru virtual public (numit, deseori, print()) responsabil cu afiarea unei reprezentri a clasei. Avantajul imediat este c orice clas derivat trebuie doar s suprancarce aceast funcie:
//exemplul 15 #include <iostream> using namespace std; class base{ int a; public: base(int x) : a(x){} virtual void print(ostream& output) const{ output << "base: " << a << endl; } }; ostream& operator<<(ostream& out_stream, const base& x){ x.print(out_stream); return out_stream; } class derived: public base{ int aa; public: derived (int x, int y): base(x), aa(y){} void print(ostream& output) const{ //extindem versiunea din clasa de baz base::print(output); output << "derived: " << aa << endl; } };

11

int main(){ base o1(5); cout << o1; derived o2(10,10); cout << o2; //nu mai trebuie suprancrcat << pentru derived! }

10. Operatori de conversie


Se tie c compilatorul trateaz orice constructor cu un singur argument ca pe un operator implicit de conversie, pe care l utilizeaz pentru a rezolva apeluri de funcii:
//exemplul 16 class T{ public: T(int); }; void f(T); f(1); //eroare de compilare? NU: f(T(1))

Nu uitai c conversiile standard au prioritate fa de conversiile utilizator:


//exemplul 17 class T{ public: T(int); }; void f(T); void f(double); f(1); //f(T(1)) sau f(double(1))? f(double(1))

De asemenea, nu uitai c operatorii suprancrcai de utilizator trebuie s aib cel puin un operand de un tip utilizator:
//exemplul 18 class T{ public: T(int); }; const T operator+ (const T&, const T&); int x, y; T r1 = x + y; //adunare de ntregi! T r2 = T(x) + y; //adunare de T!

Un constructor nu poate ns specifica: o conversie implicit de la un tip utilizator la un tip de baz (tipurile predefinite nu sunt clase); o conversie de la o clas nou la una deja definit (fr a modifica definiia vechii clase). O funcie membru T::operator X(), unde X este un tip, definete un operator de conversie de la T la X. Tipul X (spre care se convertete) este parte a numelui
12

operatorului i nu poate fi repetat ca valoare de retur. De exemplu, declaraia unui operator de conversie ctre int arat astfel: int T::operator int() const; //eroare de compilare T::operator int() const; //OK Se recomand ca aceti operatori de conversie s fie introdui n mod strict necesar; definii n exces, ei pot conduce la ambiguiti:
//exemplul 19 class T{ public: T(int); operator int() const; }; const T operator+ (const T&, const T&); T t; int i; t + i; //ambiguitate: int(t) + i sau operator+(t, T(i))? //nu uitai c tipul returnat nu se ia n calcul la rezoluia suprancrcrii!

ncheiem acest subiect cu urmtoarea observaie: ntr-o secven de conversii implicite este permis o singur conversie utilizator:
//exemplul 20 class X{ public: X(int); X(char*); }; class Y{ public: Y(int); }; class Z{ public: Z(X); }; X f(X); Y f(Y); Z g(Z); f(1); //ambiguitate: f(X(1)) sau f(Y(1))? g(X(suprancrcarea)); //OK, g( Z( X(suprancrcarea))); o singur conversie utilizator implicit: din X n Z g(Z(operatorilor)); //OK, g( Z( X(operatorilor))); o singur conversie utilizator implicit: din char* n X g(suprancrcarea operatorilor); //eroare de compilare; //dou conversii utilizator implicite: din char* n X i din X n Z! //exemplul 21 class T1{ public: operator int(){return 3;} }; class T2{ public: T2(int){} };

13

void f(T2){} ... T1 x; f(x); //eroare de compilare; // dou conversii utilizator implicite: din T1 n int i din int n T2!

11. Operatorul de indexare []


Este folosit atunci cnd se dorete ca un obiect al unei clase s poat fi utilizat asemntor cu un tablou unidimensional. Se suprancarc ntotdeauna prin funcie membru i are doar un singur argument (un index, care poate fi de orice tip). (Dac dorii s simulai tablouri multidimensionale, trebuie s suprancrcai operatorul apel de funcie:().) Nu uitai s v dotai clasa i cu versiunea const a funciei operator[] (care va fi aplicat obiectelor const)!
//exemplul 21 #include <cstdlib> #include <iostream> using namespace std; class int_array{ size_t d; int* t; public: int_array(size_t dd): d(dd){ t = new int[d]; } int& operator[](size_t d){ cout << "operator[]" << endl; return t[d]; } const int& operator[](size_t d) const{ cout << "operator[] const" << endl; return t[d]; } }; void print(const int_array& t){ cout << t[0] << endl; } int main(){ size_t dim = static_cast<size_t>(10); int_array tablou(dim); tablou[0] = 5; //operator[] cout << tablou[0] << endl; //operator[] print(tablou); //operator[] const! }

//versiunea const

Acest exemplu demonstreaz necesitatea versiunii const a funciei operator[]; desigur c, doar pentru a simula tablouri a cror dimensiune se stabilete dinamic (la

14

execuie) i nu static (la compilare), nu trebuie s reinventai roata! Folosii cu ncredere containerele din STL, precum vector.

12. Operatorul apel de funcie ()


Acest operator, suprancrcat prin funcie membru, este unic n C++, n sensul c accept oricte argumente, de orice tip (i chiar accept valori implicite pentru argumente!); de aceea, uneori, el este suprancrcat ca operator de indexare pentru tablouri multidimensionale. Utilizarea lui uzual este ns alta: el genereaz obiecte care se comport precum funciile, obiecte numite functori. n general, e vorba despre tipuri care au o singur operaie sau pentru care o operaie este predominant. Urmtorul exemplu prezint cum se poate executa o operaie asupra fiecrui membru al unui vector:
//exemplul 22 #include <iostream> #include <vector> #include <algorithm> using namespace std; class T{ public: int val; T(int x = 0): val(x){} operator int(){ return val; } }; void neg(T& t){ t.val = -t.val; } void aduna_10(T& t){ t.val += 10; } int main(){ vector<T> tablou; for (int i=1; i<=10; ++i) tablou.push_back(T(i)); for (int i=0; i<10; ++i) cout << tablou[i] << " "; cout << endl; for_each(tablou.begin(), tablou.end(), neg); for_each(tablou.begin(), tablou.end(), aduna_10); for (int i=0; i<10; ++i) cout << tablou[i] << " "; cout << endl; }

15

Cum am putea s adunm la fiecare membru al vectorului o valoare arbitrar?


//exemplul 23 class aduna{ T de_adunat; public: aduna(T x): de_adunat(x){} aduna(int x): de_adunat(T(x)){} void operator()(T& t) const { t.val += de_adunat.val; } }; for_each(tablou.begin(), tablou.end(), aduna(100)); for (int i=0; i<10; ++i) cout << tablou[i] << " "; cout << endl; for_each(tablou.begin(), tablou.end(), aduna(T(1))); for (int i=0; i<10; ++i) cout << tablou[i] << " "; cout << endl; //functor

//operaia dominant

adun(100) (respectiv, aduna(T(1))) este un obiect construit o singur dat; funcia membru operator()() va fi cea care se va apela repetat, pentru fiecare component a vectorului. Desigur, se poate utiliza i o funcie obinuit care s aib un argument suplimentar (valoarea arbitrar ce trebuie s o adunm). Ce se intmpl dac funcia trebuie s adune la componenta curent o valoare care depinde i de factori externi (de exemplu, de componenta anterioar)? Funcia trebuie s-i pstreze starea ntre apeluri i poate face asta doar prin intermediul variabilelor locale statice. Exist ns o singur copie a variabilelor locale statice, ceea ce face ca apelurile simultane ale funciei s partajeze starea, lucru evident de nedorit n medii cu mai multe fire de execuie! Functorii sunt ns obiecte i i conserv starea intern; mai mult, chiar dac au fost iniializate cu aceeai informaie, dou obiecte functori sunt distincte i deci au stri interne distincte.

13. Operatorii de selecie a membrului -> i derefereniere *


Operatorii -> i * sunt suprancrcai, de obicei mpreun, pentru a proiecta obiecte care s se comporte ca nite pointeri i care s ofere, suplimentar, funcionalitate extins. Aceste obiecte sunt cunoscute n literatura de specialitate sub
16

numele de smart pointers; i librria standard C++ definete un ablon de pointer inteligent, abon numit auto_ptr<>. Dac Ptr_to_X este o clas de obiecte pointer ctre tipul X, atunci operatorul de derefereniere * suprancrcat n Ptr_to_X execut funcionaliatea suplimentar care se dorete a fi adugat unui pointer i returneaz obiectul referit. Dei compilatorul v permite s returnai prin valoare obiectul referit, trebuie s returnai prin referin! De ce? Dac p este de tip T*, *p este o l-value de tip T! Mai mult, un obiect Ptr_to_X poate s refere nu doar obiecte de tipul X, ci i obiecte de un tip derivat din X! Dac returnai prin valoare, Ptr_to_X nu va oferi suport pentru apelarea funciilor virtuale definite n X. n concluzie, prototipul funciei operator operator*() arat astfel: X& Ptr_to_X::operator*();
//exemplul 24 #include <iostream> using namespace std; class X{ public: int data; void function() const{ cout << "function member " << endl; }; virtual void virtual_function() const{ cout << "I'm an X object" << endl; }; X(int d=0): data(d){} }; class Y: public X{ public: Y(int d=0): X(d){} void virtual_function() const{ cout << "I'm an Y object" << endl; }; }; class Ptr_to_X{ X* adr; //pointer la obiectul referit public: Ptr_to_X(X* a=0): adr(a){} //garanteaz iniializarea cu 0 X& operator*(){ //returnai prin referin! //execut functionalitate suplimentar, apoi return *adr; } }; int main(){ X o1(5); Y o2(10); Ptr_to_X p1(&o1), p2(&o2);

17

cout << (*p1).data << endl; (*p1).function(); (*p1).virtual_function(); cout << endl; cout << (*p2).data << endl; (*p2).function(); (*p2).virtual_function(); cout << endl; //Ptr_to_X p3; //*p3; return 0; }

//parantezele necesare cci operatorul . de selecie a membrului este prioritar!

//NULL //???

Ce se ntmpl dac se derefereniaz un obiect pointer null? Deoarece, n general, dereferenierea unui pointer null genereaz comportament nedefinit, avei libertatea de a trata o astfel de situaie n orice mod dorii. Operatorul -> de selecie a membrului trebuie suprancrcat printr-o funcie operator membr a clasei, care s returneze ceva pentru care s fie legal s se apeleze operatorul -> global! Din acest motiv, ea va returna pointerul ctre obiectul referit, ceea ce ofer i suport pentru apelarea funciilor virtuale definite n X; prototipul acestei funcii operator este: X* Ptr_to_X::operator->() const;
//exemplul 25 #include <iostream> using namespace std; class X{ public: int data; void function() const{ cout << "function member " << endl; }; virtual void virtual_function() const{ cout << "I'm an X object" << endl; }; X(int d=0): data(d){} }; class Y: public X{ public: Y(int d=0): X(d){} void virtual_function() const{ cout << "I'm an Y object" << endl; }; }; class Ptr_to_X{ X* adr; public: Ptr_to_X(X* a=0): adr(a){}

18

X& operator*(){ return *adr; } X* operator->() const{ return adr; } }; int main(){ X o1(5); Y o2(10); Ptr_to_X p1(&o1); Ptr_to_X p2(&o2); cout << p1->data << endl; p1->function(); p1->virtual_function(); cout << endl; //X* q1 = p1->; //X* q1 = p1.operator->(); cout << p2->data << endl; p2->function(); p2->virtual_function(); return 0; } //(p1.operator->())->data //(p1.operator->())->function() //(p1.operator->())->virtual_function() //eroare de sintax! //OK!

Se observ c funcia operator operator->() returneaz un pointer la X indiferent de membrul selectat; n acest sens, -> este un operator unar n form postfixat. Nu s-a modificat ns sintaxa, ceea ce face necesar prezena unui membru dup ->! Evident, funcia operator poate fi apelat explicit. Pentru pointerii obinuii, (p->m) == ((*p).m) == p[0].m; dac se dorete o astfel de echivalen i pentru clasele utilizator, ea trebuie furnizat explicit. Observaie: C++ permite crearea, prin suprancrcarea lui -> i ->*, a unor obiecte smart pointer ctre X; derefereniind un astfel de obiect, ar trebui s obinem un obiect smart reference ctre X. Aceast simetrie nu este ns realizabil datorit faptului c . i .* nu pot fi suprancrcai!

14. Operatorul de selecie prin pointer la membru ->*


Un pointer la membru identific un membru al unei clase i se obine aplicnd operatorul de adres & unui nume complet calificat de membru. Pointerii la funcii membru sunt utili atunci cnd (analog pointerilor la funcii) trebuie s apelm o funcie membru fr a-i cunoate numele.
19

Dac un pointer la funcie este o adres de memorie, un pointer la funcie membru virtual este un index (sau un offset); cu alte cuvinte, el nu depinde de localizarea obiectului n memorie! n consecin, un pointer la funcie membru virtual poate fi transferat ntre spaii de adrese diferite (evident, cu condiia ca acestea s foloseasc acelai model-obiect)! Pointerii la funcii i pointerii la funcii membru nevirtuale nu pot fi transferai ntre spaii de adrese. Un pointer la membru poate fi utilizat doar n combinaie cu un obiect; operatorii .* i ->* permit programatorului s exprime aceast compinaie. Dac pm este un pointer la membru, o este un obiect i p este un pointer la o, atunci o.*pm leag pm de obiectul o, n timp ce p->*pm leag pm de obiectul referit de p. Un membru static nu este asociat cu un obiect anume, deci pointerii la membri statici sunt pointeri obinuii.
//exemplul 26 #include <iostream> using namespace std; class X{ public: int data; void function() const{ cout << "function member " << endl; }; virtual void virtual_function() const{ cout << "I'm an X object" << endl; }; X(int d=0): data(d){} }; typedef void (X::* PFM)()const; class Y: public X{ public: Y(int d=0): X(d){} void virtual_function() const{ cout << "I'm an Y object" << endl; }; }; int main(){ X o1(5); Y o2(10); PFM pf1 = &X::function; PFM pf2 = &X::virtual_function; X *p1=&o1, *p2=&o2; //p1 i p2 sunt pointeri built-in la X (p1->*pf1)(); //observai utilizarea operatorului apel de funcie () (p1->*pf2)(); (p2->*pf1)(); (p2->*pf2)(); }

20

Ca i ->, operatorul ->* se suprancarc prin funcie membru i este utilizat pentru o simula comportamentul pointer-la-membru pentru obiectele smart pointer; ->* este ns un operator binar. Cheia implementrii funciei operator ->*() const n observaia c aceast funcie trebuie s returneze un obiect pentru care s poat fi apelat funcia operator()() cu argumentele funciei membru ce trebuie apelat; o clas auxiliar ne va ajuta s definim acest obiect.
//exemplul 27 #include <iostream> using namespace std; class X{ public: int data; void function() const{ cout << "function member " << endl; }; virtual void virtual_function() const{ cout << "I'm an X object" << endl; }; X(int d=0): data(d){} }; class Y: public X{ public: Y(int d=0): X(d){} void virtual_function() const{ cout << "I'm an Y object" << endl; }; }; typedef void (X::* PFM)()const; //alias pt. pointer la funcie membru const, fr argumente i care nu returneaz class Ptr_to_X{ X* adr; public: Ptr_to_X(X* a=0): adr(a){} X& operator*(){ return *adr; } X* operator->() const{ return adr; } protected: //operator ->*() trebuie sa returneze un obiect pt. care este definit operator()() class auxiliar{ X* p; PFM pfm; public: auxiliar(X* const p1, PFM p2):p(p1), pfm(p2){} auxiliar(const auxiliar& other){ p = other.p; pfm = other.pfm; } void operator()() const{ (p->*pfm)(); //apelul propriu-zis al functiei membru } }; //sfrit definiie pt. clasa auxiliar

21

public: auxiliar operator->*(PFM pfm){ return auxiliar(adr, pfm); } }; int main(){ X o1(5); Y o2(10); Ptr_to_X p1(&o1); Ptr_to_X p2(&o2); PFM pf1 = &X::function; PFM pf2 = &X::virtual_function; (p1->*pf1)(); (p1->*pf2)(); (p2->*pf1)(); (p2->*pf2)(); return 0; } //aceeai sintax cu cea a pointerilor built-in //p1 i p2 sunt acum obiecte smart pointer la X //suprancrcarea ->* //apelare automata a lui auxiliar::operator()()

Am suprancrcat ->* doar pentru funcii membru fr argumente i care nu returneaz; cu ajutorul template-urilor se poate generaliza acest mecanism. Norocel PETRACHE Bibliografie: 1. Bjarne Stroustrup, The C++ Programming Language 2. ANSI/ISO C++ Professional Programmers Handbook 3. Scott Meyers, Effective C++ 4. Scott Meyers, More Effective C++ 5. Herb Sutter Exceptional C++ 6. Bruce Eckel Thinking in C++

22

S-ar putea să vă placă și