Regula 20 preferă trecerea prin referire la trecerea prin valoare const - eficace

Regula 20: Prefer trece prin referire la trecerea Const de valoare

În mod implicit, C ++ obiectele sunt transmise la funcția, și returnează o funcție de valoarea (proprietate moștenită de la C). Dacă nu se specifică altfel, parametrii funcției sunt inițializate cu copii de argumente reale, iar după apelarea funcției, programul primește o copie a valorii returnate de o funcție. Copii a produs un constructor de copiere. Prin urmare, transmisia poate fi operație semnificativ lot. De exemplu, ia în considerare următoarea ierarhie de clase:

Persoana (); // parametri sunt omise din motive de simplificare

Persoana (); // vezi Regula 7 -. De ce virtuale

Clasa de Student: persoană publică

Student (); // parametri sunt omise aici

Acum, uita-te la următorul cod, care se numește funcția validateStudent care are un student argument (de valoare) și returnează semnul corectitudinii sale:

bool validateStudent (Student s); // functie are

// Student la valoarea

Plato Student; // Platon învățat de la Socrate

bool platoIsOk = validateStudent (plato); // apela funcția

Ce se întâmplă atunci când apelați această funcție?

În mod evident, constructorul copy Student este chemat pentru a inițializa parametrul Plato. De asemenea, este clar că e distrus atunci când se întorc din VALIDATI-Student. Prin urmare, parametrul de transmisie prin valoarea acestei funcții costă într-un singur exemplar constructor apel student și un student apel destructor.

Dar asta nu e tot. obiect Student conține în sine două obiecte: șir de caractere, astfel încât de fiecare dată când construi un obiect student trebuie, de asemenea, construi și aceste două obiecte. clasa Student moștenește clasa Person, astfel încât de fiecare dată când proiectarea obiect Student, aveți nevoie pentru a proiecta și obiect Person. Dar un obiect Person conține două obiect string, astfel încât fiecare persoană de construcție implică două apeluri constructor șir. Astfel, transferul obiectului Student valoare conduce la un apel copie student constructor, copiați apel constructor de o persoană și patru apeluri string constructori copy. Atunci când o copie a obiectului Student este distrus, fiecare apel constructor corespunde apel destructor, astfel încât costul total al trecatorii Student valoare este de șase constructori și șase destructori!

Ei bine, acesta este comportamentul corect și dorit. La sfârșitul zilei, doriți ca toate obiectele sunt inițializate și distruse în mod corespunzător. Cu toate acestea, ar fi frumos să găsească o cale de a sări peste toate aceste constructori apelurile și destructori. Există o cale! Acesta - care trece prin referire la o constantă:

bool validateStudent (Student Const s);

Parametrul de referință evită, de asemenea, „tăierea“ a problemei (feliere). Când obiectul de clasă derivată este transferată (de valoare) ca obiect constructor clasa de baza este numită clasă de bază de copiere, și acele părți care aparțin derivatul „tăiat“. Tocmai ai avea un simplu obiect de clasă de bază - care este destul de firesc, din moment ce a creat un constructor clasa de baza. Este aproape întotdeauna nu ceea ce ai nevoie. De exemplu, să presupunem că lucrați cu un set de clase pentru implementarea unui sistem grafic fereastră:

Numele std :: string () const; // returnează numele ferestrei

afișare virtuală void () const; // Desenam fereastră și conținutul

WindwoWithScrollBars clasă: Fereastra publică

afișare virtuală void () const;

Toate obiectele de ferestre de clasă au un nume pe care le puteți obține prin numele funcției, și toate ferestrele pot fi afișate, așa cum este indicat de prezența funcțiilor de afișare. Faptul că afișajul - o funcție virtuală, spune că modul de a afișa simplu baza de obiecte de ferestre de clasă poate fi diferită de metoda de afișare obiectelor WindowWithScrollBar (a se vedea regulile 34 și 36.).

Acum, să presupunem că doriți să scrie o funcție care va imprima numele ferestrei și apoi afișa. Acesta este un mod greșit de a scrie o astfel de funcție:

void printNameAndDisplay (Fereastră w) // greșit! parametru

Să vedem ce se întâmplă, dacă sunați această funcție, care trece ea WindowWithScrollBar obiect:

w parametru va fi construit - acesta este trecut prin valoare, amintiți? - ca obiect de ferestre, precum și toate informațiile suplimentare, ceea ce face obiectul WindowWithScrollBar, acesta va fi tăiat. În interiorul printNameAndDisplay w se va comporta întotdeauna ca un obiect al ferestrei de clasă (deoarece este un obiect de ferestre de clasă), indiferent de tipul obiectului este, de fapt trecut la funcția. În special, funcția de afișare de apel în termen de printNameAndDisplay provoca întotdeauna Window :: afișare, niciodată - WindowWithScrollBar :: afișare.

Remediu „tăiere“ - w trece prin referire la o constantă:

void printNameAndDisplay (ferestre const w) // opțiune corectă

Acum w se comportă corect, indiferent de ceea ce poate reprezenta o fereastră în realitate.

Dacă te uiți „sub capotă» C ++, veți vedea că referințele sunt de obicei puse în aplicare ca indicii, astfel încât transferul de ceva prin referință înseamnă, de obicei, indicatorul de transmisie. Ca rezultat, obiectele încorporate de tip (de exemplu, int) este întotdeauna transmite mai eficient decât semnificativ prin referință. Prin urmare, pentru tipurile încorporate, dacă aveți posibilitatea de a alege - să treacă prin valoare sau prin referire la o constantă, are sens să aleagă o trecere de valoare. Același sfat se aplică iteratorii și funcția obiecte STL, deoarece acestea sunt proiectate în mod specific pentru valoarea transferurilor. Programatorii de punere în aplicare iteratori și obiecte de funcții, sunt responsabile pentru, pentru a asigura eficiența transmiterii valorii lor și de a exclude „Cut“. Acesta este un exemplu de modul în care sunt schimbate regulile în funcție de partea pe care C ++ (cm. 1, în general).

Chiar și atunci când obiectele mici au ieftin în constructori de copiere, ele pot avea în continuare un impact asupra performanței. Unele compilatoare trata built-in și tipuri definite de utilizator în diferite moduri, chiar dacă acestea au aceeași reprezentare internă. De exemplu, unele compilatoare nu plasați obiecte constând dintr-o singură dublă în registre, chiar dacă sunt dispuși să plaseze valoarea built-in de tip dublu. În astfel de cazuri, este mai bine să treacă obiecte de referință, deoarece compilatorul este cu siguranță dispus să pună în indicatorul registru (care pune în aplicare un link).

Un alt motiv pentru mici tipuri definite de utilizator nu sunt neapărat bune pentru transmiterea valorii constă în faptul că dimensiunea lor se pot schimba. Tipul, care este mic astăzi, ar putea crește în viitor, din cauza punerii în aplicare sale interne se poate schimba. Situația se schimbă, chiar dacă trece la o punere în aplicare diferită a C ++. De exemplu, în unele implementări, tipul string din biblioteca standard este de șapte ori mai mult decât în ​​altele.

În general vorbind, singurele tipuri pentru care se poate presupune că transferul de valoare va fi ieftin - este built-in tipuri, precum iteratorii și obiecte funcționale STL. Pentru orice altceva, urmați sfatul regulilor și trec parametrii prin referire la o valoare constantă în locul transmisiei.