Acum, când suntem clare cu privire la funcțiile generate de compilator, Regula celor trei și Regula celor cinci, să le folosim pentru a reflecta la modul de utilizare a funcției „= implicite” pentru a avea un cod expresiv și corect.

celor cinci

Într-adevăr, C ++ 11 a adăugat posibilitatea de a cere de la compilator să scrie o implementare implicită pentru aceste metode ale unei clase:

Dar compilatorul poate genera aceste funcții, chiar dacă nu le specificăm în interfață. Am văzut că această caracteristică C ++ avea unele complexități, dar în cazul de mai sus oricum, codul este perfect echivalent cu acesta:

Aceasta ridică o întrebare: dacă compilatorul este capabil să ofere o implementare implicită, ar trebui să scriem = implicit pentru a fi mai explicit chiar și atunci când acest lucru nu schimbă codul generat? Sau este o verbozitate gratuită? Ce cale este mai expresivă?

Am avut dezbaterea cu colegii mei (pălărie pentru ei), am săpat în jur pentru a-mi da seama că a fost o dezbatere fierbinte: Ghidurile de bază C ++ au o opinie, Scott Meyers are o opinie și nu sunt de acord cu fiecare alte. Să vedem despre ce este vorba.

C ++ Core Guidelines & R. Martinho Fernandes: The Rule of Zero

Liniile directoare de bază C ++ sunt foarte clare în legătură cu această întrebare, ghidul de deschidere privind constructorii precizând:

C.20: Dacă puteți evita definirea operațiilor implicite, faceți.

Dreapta. Destul de clar. Acum, care este rațiunea din spatele acestui ghid?

Motiv Este cel mai simplu și oferă cea mai curată semantică. [Dacă toți membrii] au toate funcțiile speciale, nu este nevoie de alte lucrări.

Și ghidul continuă spunând că acest lucru este cunoscut sub numele de „Regula zero„.

Acest termen a fost inventat de R. Martinho Fernandes, într-o postare de blog din 2012 (mulțumesc utilizatorului Lopo și utilizatorului Redditphere991 pentru că a dezgropat postarea).

Care este exact Regula zero? Merge astfel: Clasele care declară destructori personalizați, constructori de copiere/mutare sau operatori de atribuire de copiere/mutare ar trebui să se ocupe exclusiv de proprietate. Alte clase nu trebuie să declare destructori personalizați, constructori de copiere/mutare sau operatori de atribuire de copiere/mutare (Rule of Zero ușor reformulat de Scott Meyers).

Conform Rule of Zero, există două opțiuni în ceea ce privește funcțiile pe care compilatorul le poate genera: fie că toate au o implementare non-trivială care se ocupă de proprietate, fie că niciuna dintre ele nu este declarată.

Cu excepția faptului că, dacă îl priviți cu atenție, Rule of Zero nu spune nimic despre constructorul implicit X (). Menționează doar cele 5 funcții care altfel participă la Regula celor Cinci. Ca o reamintire, Regula celor cinci spune că dacă una dintre cele 5 funcții de gestionare a resurselor (constructori de copiere/mutare, operatori de alocare copiere/mutare, destructor) a avut o implementare non-banală, celelalte ar trebui să aibă cu siguranță o implementare non-banală de asemenea.

Deci, ce zici de constructorul implicit? Dacă implementarea sa este banală, ar trebui să o declarăm cu = implicit sau să nu o declarăm deloc și să lăsăm compilatorul să facă treaba?

Dar C ++ Core Guideline C.20 pare să ne încurajeze să nu o declarăm nici:

C.20: Dacă puteți evita definirea operațiilor implicite, faceți.

Încă destul de clar.

Scott Meyers: Regula celor cinci valori implicite

Scott Meyers scrie ca răspuns la Rule of Zero că prezintă un risc.

Într-adevăr, declararea oricăreia dintre cele 5 funcții are un efect secundar asupra generării automate a operațiunilor de mutare. Un efect secundar destul de dur, deoarece dezactivează generarea automată a operațiunilor de mutare. (Dacă vă întrebați de ce operațiile de mutare în mod specific, aruncați o privire la actualizarea funcțiilor generate de compilator, regula celor trei și regula celor cinci).

În special, dacă adăugați un destructor la clasă:

Apoi își pierde operațiunile de mutare. DAR nu își pierde operațiunile de copiere! Deci, codul clientului va continua să se compileze, dar va apela în tăcere copiere în loc de mutare. Acest lucru nu este bun.

De fapt, dacă declarați destructorul în mod explicit, chiar dacă utilizați implementarea generată implicit:

Apoi, clasa își pierde operațiunile de mutare!

Apărarea regulii zero

Un argument al susținătorilor Rule of Zero pentru a răspunde îngrijorării lui Scott este: de ce am implementa doar un destructor pentru o clasă? În acest sens, Scott aduce în discuție cazul de utilizare al depanării. De exemplu, poate fi util să puneți un punct de întrerupere sau o urmă în destructorul unei clase pentru a urmări în timp de rulare ceea ce se întâmplă într-un program provocator.

Un alt argument al susținătorilor Regulei zero împotriva îngrijorării lui Scott este că compilatorul este oricum capabil să surprindă situația riscantă cu un avertisment. Într-adevăr, cu steagul -Wdeprecateed, zăngăni emite următorul avertisment pentru clasa X de mai sus:

Și când încercăm să invocăm o operație de mutare pe acea clasă care implementează în mod silențios copia:

De asemenea, primim un avertisment:

Este frumos, dar este doar un avertisment, nu este standard și doar din clang îl emite din câte știu eu. Standardul menționează doar că „într-o viitoare revizuire a acestui standard internațional, aceste definiții implicite ar putea fi șterse”. A existat o propunere pentru standardul de a face acest comportament oficial ilegal, dar nu a fost acceptat.

Regula celor cinci valori implicite

În schimb, Scott Meyers susține o altă regulă, regula celor cinci valori implicite: declarați întotdeauna cele 5 funcții de gestionare a resurselor. Și dacă sunt banale, utilizați = implicit:

Rețineți că, la fel ca în Ghidurile de bază C ++, constructorul implicit slab X () a fost lăsat în afara discuției.

Cu toate acestea, dacă urmăm regula celor cinci valori implicite, nu mai există multă alegere pentru constructorul implicit. Într-adevăr, dacă există cel puțin un alt constructor declarat, compilatorul nu generează automat constructorul implicit. Și aici nu avem unul, ci doi constructori declarați: constructorul de copiere și constructorul de mutare.

Deci, cu Regula celor cinci valori implicite, dacă dorim un constructor implicit trivial, atunci trebuie să-l declarăm:

Deci poate că ar trebui să numim asta Regula celor șase valori implicite. Oricum.

Interfețe bune pentru buni programatori

Nu cred că dezbaterea a fost câștigată de niciuna dintre părți în acest moment.

Aplicarea regulilor celor cinci (sau șase) valori implicite produce mai mult cod pentru fiecare interfață. În cazul interfețelor foarte simple, cum ar fi o structură care grupează câteva obiecte împreună, care pot dubla sau tripla dimensiunea interfeței și nu exprimă atât de mult.

Ar trebui să producem tot acest cod pentru a face interfața explicită?

Pentru mine, aceasta se reduce la întrebarea ce vor crede programatorii în clasă uitându-vă la interfața sa.

Dacă cunoașteți regulile C ++, veți ști că o clasă care nu declară niciuna dintre cele 6 metode exprimă faptul că le are pe toate. Și dacă le declară pe toate, cu excepția operațiilor de mutare, atunci este probabil o clasă care provine din C ++ 98 și, prin urmare, nu respectă semantica mutării (care este, de altfel, un alt argument în favoarea Regulei zero: cine știe Ce va fi în viitor? Poate că în C ++ 29 va exista un constructor &&&, iar regula zero va exprima că clasa dorește valori implicite pentru tot, inclusiv &&&).

Riscul este ca cineva să proiecteze o clasă fără să știe ce fac sau că un cititor al codului nu știe suficient C ++ pentru a deduce ce ar putea face o clasă. Și nu cred că ar trebui să încărcăm codul cu o plasă de siguranță de 5 = funcții ed implicite pentru fiecare tip de bază de cod.

În schimb, ar trebui să presupunem că

  • colegii dezvoltatori știu ce fac și le pasă de mesajele exprimate (sau implicite) de interfețele lor,
  • colegii dezvoltatori știu suficient C ++ pentru a citi ce exprimă (sau implică) o interfață.

Poate te gândești „oh, știu un tip junior care demonstrează complet că aceste presupuneri sunt greșite”. Și într-adevăr, cu toții trebuie să începem ca începători. Dar chestia este că trebuie să ne străduim să transformăm aceste ipoteze în realitate.

Acesta este punctul de revizuire a codurilor, instruiri, cotidiene, mentorat, programare pereche, cărți și așa mai departe. Aceasta este o investiție, dar cred că trebuie să ne ridicăm la nivelul codului, și nu invers.

Știu că este o întrebare controversată și mi-ar plăcea să vă aud părerea despre aceasta. Crezi că ar trebui să scriem cod de parcă toată lumea din proiect ar fi la curent cu regulile C++?

Pentru a încheia, voi lăsa cuvântul de închidere lui Arne Mertz, care a rezumat dezbaterea cu o regulă la care toată lumea este de acord, „Regula tuturor sau a nimicului”:

Atâta timp cât poți, rămâi la Regula zero, dar dacă trebuie să scrii cel puțin unul dintre cei cinci mari, restul implicit.

Acum, să luăm o pauză și să luăm o băutură răcoritoare cu zero calorii. Adică apă, desigur.

Ați putea dori, de asemenea

  • Funcții generate de compilator, regula celor trei și regula celor cinci
  • Răspândiți cunoștințe în compania dvs. cu „C ++ zilnic” dvs.
  • Ce cărți să citești pentru a te îmbunătăți în C++
Devino Patron!
Distribuie aceasta postare! & nbsp & nbsp & nbsp & nbsp Nu doriți să ratați ? Urma: & nbsp & nbsp

Obțineți o carte electronică gratuită pe indicatorii inteligenți C ++

Obțineți o carte electronică gratuită de peste 50 de pagini care vă va învăța aspectele de bază, medii și avansate ale indicatoarelor inteligente C ++, abonându-vă la lista noastră de corespondență! În plus, veți primi, de asemenea, actualizări regulate pentru a vă face codul mai expresiv.