Documente Academic
Documente Profesional
Documente Cultură
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.
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 +=!
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
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!
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; } };
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!
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.
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
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! }
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!
//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.
15
//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.
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; }
//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!
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