Standardul C++

corectitudinea Const

ce este “corectitudinea const”?

un lucru bun. Înseamnă utilizarea cuvântului cheie const pentru a preveni mutarea obiectelor const.

de exemplu, dacă doriți să creați o funcție f() care a acceptat o std::string , plus doriți să promit apelanținu pentru a schimba std::string apelantului care devine trecut la f(), puteți avea f() primi std::string parametru…

  • void f1(const std::string& s); // treceți prin referință-la-const
  • void f2(const std::string* sptr); // treceți prin pointer-to-const
  • void f3(std::string s); // treceți prin valoarea

în cazurile pass by reference-to – const și pass by pointer-to – const, orice încercare de a schimba std::string apelantului în cadrul funcțiilor f() ar fi marcată de compilator ca o eroare la compilare-timp. Această verificare este efectuată în întregime la compilare: nu există spațiu de rulare sau cost de viteză pentru const. În cazul pass by value (f3()), funcția apelată primește o copie a apelantului std::string. Aceasta înseamnă că f3() își poate schimba localcopia, dar copia este distrusă atunci când f3() revine. În special, f3() nu poate modifica obiectul std::stringal apelantului.

ca exemplu opus, să presupunem că doriți să creați o funcție g() care să accepte un std::string, dar doriți să anunțați apelanții că g() ar putea schimba obiectul std::string al apelantului. În acest caz, puteți avea g() primi std::string parametru…

  • void g1(std::string& s); // treceți prin referință-la-non-const
  • void g2(std::string* sptr); // treceți prin pointer-to-non-const

lipsa const în aceste funcții îi spune compilatorului că li se permite (dar nu li se cere) să schimbe obiectul std::string al formatorului. Astfel, ei își pot trece std::string la oricare dintre funcțiile f(), dar numai f3()(cel care primește parametrul său “prin valoare”) poate trece std::string la g1() sau g2(). Dacă f1() sau f2() trebuie să apeleze fie funcția g(), o copie locală a obiectului std::string trebuie transmisă funcției g(); parametrii la f1() sau f2() nu pot fi transmise direct nici funcției g(). De exemplu.,

void g1(std::string& s);void f1(const std::string& s){ g1(s); // Compile-time Error since s is const std::string localCopy = s; g1(localCopy); // Okay since localCopy is not const}

în mod natural, în cazul de mai sus, orice modificări pe care g1() le face sunt făcute obiectului localCopy care este local la f1().în special, nu se vor face modificări parametrului const care a fost trecut prin referire la f1().

cum este “corectitudinea const” legată de siguranța de tip obișnuit?

declararea const-ness a unui parametru este doar o altă formă de siguranță de tip.

dacă găsiți siguranța de tip obișnuit vă ajută să corectați sistemele (o face; mai ales în sistemele mari), veți găsiconst corectitudinea ajută, de asemenea.

beneficiul corectitudinii const este că vă împiedică să modificați din greșeală ceva la care nu vă așteptațiar fi modificat. Veți ajunge să vă decorați codul cu câteva apăsări de taste suplimentare (cuvântul cheie const), cu avantajul că îi spuneți compilatorului și altor programatori o parte suplimentară de informații semantice importante— informații pe care compilatorul le folosește pentru a preveni greșelile și alți programatori folosesc ca documentație.

conceptual vă puteți imagina că const std::string, de exemplu, este o clasă diferită de cea obișnuită std::string,deoarece variantaconst lipsește conceptual diferitele operații mutative care sunt disponibile în varianta non- const. De exemplu, vă puteți imagina conceptual că un const std::string pur și simplu nu are un operator de atribuire+= sau orice alte operații mutative.

ar trebui să încerc să corectez lucrurile “mai devreme” sau “mai târziu”?

la foarte, foarte, foarte început.

back-patching const corectitudinea are ca rezultat un efect de bulgăre de zăpadă: fiecare const adăugați “aici” necesită încă patru pentru a fi adăugat “acolo.”

adăugați const devreme și des.

ce înseamnă “const X* p”?

înseamnă p indică un obiect de clasă X, dar p nu poate fi folosit pentru a schimba acel X obiect (în mod natural par putea, de asemenea, să fie NULL).

citiți-l de la dreapta la stânga: “p este un pointer către un X care este constant.”

de exemplu, dacă clasa X are o funcție de membru const, cum ar fi inspect() const, este în regulă să spunemp->inspect(). Dar dacă clasa X are o funcție de membru non-const numită mutate(), este anerror dacă spui p->mutate().

în mod semnificativ, această eroare este prinsă de compilator la compilare-timp — nu se fac teste de execuție. Asta înseamnă că const nu încetinește programul dvs. și nu necesită să scrieți cazuri suplimentare de testare pentru a verifica lucrurile în timpul rulării-thecompiler lucrează la compilare.

care este diferența dintre “const X* p”, “X* const p” și “const X* const p”?

citiți declarațiile indicatorului de la dreapta la stânga.

  • const X* p înseamnă ” p indică un X care este const“: obiectul X nu poate fi modificat prinp.
  • X* const p înseamnă ” p este un const pointer la un X care este non – const“: nu puteți schimba indicatorul p în sine, dar puteți schimba obiectul Xprin p.
  • const X* const p înseamnă “p este un const pointer la un X care este const“: nu puteți schimba indicatorul p în sine și nici nu puteți schimba obiectul Xprin p.

și, oh, da, am menționat să vă citesc declarațiile indicatorului de la dreapta la stânga?

ce înseamnă “const X& x”?

înseamnă x pseudonime un obiect X, dar nu puteți schimba acel obiect Xprin x.

Citește-l de la dreapta la stânga: “x este o referință la un X care este const.”

de exemplu, dacă clasa X are o funcție de membru const, cum ar fiinspect() const, este în regulă să spunem x.inspect(). Dar dacă clasa X are o funcție de membru non-const numită mutate(), este o eroaredacă spui x.mutate().

acest lucru este în întregime simetric cu indicii către const, inclusiv faptul că compilatorul face toate verificările la compilare, ceea ce înseamnă că const nu încetinește programul dvs. și nu necesită să scrieți cazuri suplimentare de testare pentru a verifica lucrurile în timpul rulării.

ce înseamnă “x const& x” și “X const* p”?

X const& x este echivalent cu const X& x, iarX const* xeste echivalent cu const X* x.

unii oameni preferă stilul const-on-the-right, numindu-l “consecvent const” sau, folosind un termen inventat de Simon Brand, “Est const.”Într-adevăr, stilul” Est const “poate fi mai consistent decât alternativa: stilul” Est const ” pune întotdeauna const în dreapta a ceea ce constituie, în timp ce celălalt stil pune uneori const în stânga și alteori în dreapta (pentru const declarații pointer și const funcții membre).

cu stilul “Est const“, o variabilă locală care este const este definită cu constdin dreapta:int const a = 42;. În mod similar, o variabilă static care este const este definită ca static double const x = 3.14;.Practic, fiecare const ajunge în dreapta lucrului pe care îl constituie, inclusiv const care trebuie să fie în dreapta: const declarații de pointer și cu o funcție de membru const.

stilul” Est const ” este, de asemenea, mai puțin confuz atunci când este utilizat cu pseudonime de tip: de ce foo și bar au tipuri diferite aici?

using X_ptr = X*;const X_ptr foo;const X* bar;

utilizarea stilului “East const” face acest lucru mai clar:

using X_ptr = X*;X_ptr const foo;X* const foobar;X const* bar;

este mai clar aici că foo și foobar sunt de același tip și că bar este un tip diferit.

stilul “Est const” este, de asemenea, mai consistent cu declarațiile indicatorului. Contrast stilul tradițional:

const X** foo;const X* const* bar;const X* const* const baz;

cu stilul “East const

X const** foo;X const* const* bar;X const* const* const baz;

în ciuda acestor beneficii, stilul const -on-the-right nu este încă popular, astfel încât codul legacy tinde să aibă stilul tradițional.

are “x& const x” vreun sens?

nu, este o prostie.

pentru a afla ce înseamnă Declarația de mai sus, citiți-o de la dreapta la stânga: “x este o referință const la un X“. Dar acest lucru este redundant — referințele sunt întotdeauna const, în sensul că nu puteți reseta niciodată areference pentru a o face să se refere la un obiect diferit. Niciodată. Cu sau fără const.

cu alte cuvinte, “X& const x” este echivalent funcțional cu ” X& x“. Din moment ce nu câștigi nimic adăugândconst după &, nu ar trebui să — l adaugi: va deruta oamenii – const îi va face pe unii să creadă că X este const, ca și cum ai fi spus “const X& x“.

ce este o “funcție de membru const”?

o funcție membru care inspectează (mai degrabă decât mută) obiectul său.

o funcție membru const este indicată printr-un sufix const imediat după lista parametrilor funcției membru. Funcțiile membre cu un sufix const se numesc ” const funcții membre “sau” inspectori.”Funcțiile membre fără un sufix const se numesc” funcții membre non-const “sau” mutatori.”

class Fred {public: void inspect() const; // This member promises NOT to change *this void mutate(); // This member function might change *this};void userCode(Fred& changeable, const Fred& unchangeable){ changeable.inspect(); // Okay: doesn't change a changeable object changeable.mutate(); // Okay: changes a changeable object unchangeable.inspect(); // Okay: doesn't change an unchangeable object unchangeable.mutate(); // ERROR: attempt to change unchangeable object}

încercarea de a apela unchangeable.mutate() este o eroare prinsă la momentul compilării. Nu există spațiu de rulare sau speedpenalty pentru const și nu este nevoie să scrieți cazuri de testare pentru a le verifica în timpul rulării.

trailing const pe inspect() funcția membru ar trebui să fie folosit pentru a însemna metoda nu se va schimba object ‘ sabstract (client-vizibil) de stat. Acest lucru este ușor diferit de a spune că metoda nu va schimba “biții brute” ai obiectului struct. Compilatorii C++ nu au voie să ia interpretarea “bitwise” decât dacă pot rezolva problema de aliere, care în mod normal nu poate fi rezolvată (adică ar putea exista un alias non-const care ar putea modifica starea obiectului). O altă perspectivă (importantă) din această problemă de aliasing: arătând spre un obiect cu un pointer-to – const nu garantează că obiectul nu se va schimba; doar promite că obiectul nu se va schimba prin acel pointer.

care este relația dintre o funcție de returnare prin referință și o funcție de membru const?

dacă doriți să returnați un membru al obiectului dvs. this prin referință dintr-o metodă inspector, ar trebui să îl returnați folosind referință la const (const X& inspect() const) sau prin valoare (X inspect() const).

class Person {public: const std::string& name_good() const; // Right: the caller can't change the Person's name std::string& name_evil() const; // Wrong: the caller can change the Person's name int age() const; // Also right: the caller can't change the Person's age // ...};void myCode(const Person& p) // myCode() promises not to change the Person object...{ p.name_evil() = "Igor"; // But myCode() changed it anyway!!}

vestea bună este că compilatorul te va prinde adesea dacă greșești. În special, dacă returnați accidental un membru al obiectului this prin referință non-const, cum ar fi în Person::name_evil()de mai sus, compilatorul îl va detecta adesea și vă va oferi o eroare de compilare în timp ce compilați măruntaiele, în acest caz,Person::name_evil().

vestea proastă este că compilatorul nu vă va prinde întotdeauna: există unele cazuri în care compilatorul pur și simplu nu vă va da niciodată un mesaj de eroare în timpul compilării.

traducere: trebuie să te gândești. Dacă asta te sperie, găsește o altă linie de lucru; “gândește” nu este un cuvânt din patru litere.

amintiți-vă “const filosofia” răspândit în această secțiune: o const funcția de membru mustnot schimba (sau permite un apelant pentru a schimba) this starea logică a obiectului (aka stat abstract aka meanwisestate). Gândiți-vă la ceea ce înseamnă un obiect, nu la modul în care este implementat intern. Vârsta și numele unei persoane sunt logiceo parte a persoanei, dar vecinul și angajatorul persoanei nu sunt. O metodă inspector care returnează o parte din starea logică / abstractă / înțeleaptă a obiectuluithis nu trebuie să returneze un pointer (sau referință) non-const la acea parte,independent de faptul că acea parte este implementată intern ca un membru de date direct încorporat fizic în obiectul this sau în alt mod.

care este treaba cu”const-overloading”?

const supraîncărcarea vă ajută să obțineți const corectitudinea.

const supraîncărcarea este atunci când aveți o metodă inspector și o metodă mutatorcu același nume și același număr de și tipuri de parametri. Cele două metode distincte diferă doar prin faptul că inspectorul este const și mutatorul este non-const.

cea mai obișnuită utilizare a supraîncărcării const este cu operatorul de indice. În general, ar trebui să încercați să utilizați unul dintresabloane standard de containere, cum ar fi std::vector, dar dacă trebuie să vă creați propria clasă care are un subscriptoperator, iată regula generală: operatorii de abonamente vin adesea în perechi.

class Fred { /*...*/ };class MyFredList {public: const Fred& operator (unsigned index) const; // Subscript operators often come in pairs Fred& operator (unsigned index); // Subscript operators often come in pairs // ...};

operatorul de indexare const returnează o referință const, astfel încât compilatorul va împiedica apelanții să schimbe/schimbe accidental Fred. Operatorul de subscript non – const returnează o referință non – const , care este modul dvs. de a spune apelanților dvs. (și compilatorului) că apelanților dvs. li se permite să modifice obiectul Fred.

când un utilizator al clasei dvs. MyFredListapelează operatorul indicatorului, compilatorul Selectează ce suprasarcină să apeleze pe baza constanței MyFredList lor. Dacă apelantul are un MyFredList a sau MyFredList& a, atuncia va apela operatorul de abonamente non- const , iar apelantul va ajunge la o referință non-const la un Fred:

de exemplu, să presupunem că class Fred are o metodă inspector inspect() const și o metodă mutator mutate():

void f(MyFredList& a) // The MyFredList is non-const{ // Okay to call methods that inspect (look but not mutate/change) the Fred at a: Fred x = a; // Doesn't change to the Fred at a: merely makes a copy of that Fred a.inspect(); // Doesn't change to the Fred at a: inspect() const is an inspector-method // Okay to call methods that DO change the Fred at a: Fred y; a = y; // Changes the Fred at a a.mutate(); // Changes the Fred at a: mutate() is a mutator-method}

cu toate acestea, dacă apelantul are un const MyFredList a sau const MyFredList& a, atunci a va apela subscriptoperatorul const, iar apelantul va ajunge cu o referință constla un Fred. Acest lucru permite apelantului să inspecteze Fred la a, dar împiedică apelantul să mute/schimbe din greșeală Fredla a.

void f(const MyFredList& a) // The MyFredList is const{ // Okay to call methods that DON'T change the Fred at a: Fred x = a; a.inspect(); // Compile-time error (fortunately!) if you try to mutate/change the Fred at a: Fred y; a = y; // Fortunately(!) the compiler catches this error at compile-time a.mutate(); // Fortunately(!) the compiler catches this error at compile-time}

Const supraîncărcarea pentru subscript – și funcall-operatorii este ilustrat aici,aici, aici, aici, și aici.

desigur, puteți utiliza și const -supraîncărcarea pentru alte lucruri decât operatorul de indice.

cum mă poate ajuta să proiectez clase mai bune dacă disting starea logică de starea fizică?

pentru că vă încurajează să vă proiectați clasele din exterior, mai degrabă decât din interior, ceea ce face ca clasele și obiectele dvs. să fie mai ușor de înțeles și de utilizat, mai intuitive, mai puțin predispuse la erori și mai rapide. (Bine, asta eo ușoară supra-simplificare. Pentru a înțelege toate if ‘S și’ s și dar ‘ s, va trebui doar să citiți restul acestui răspuns!)

să înțelegem acest lucru din interior-exterior — va (ar trebui) să proiecteze clasele de la outside-in, dar dacă sunteți nou la acest concept, este mai ușor de înțeles de la theinside-out.

în interior, obiectele dvs. au o stare fizică (sau beton sau bit). Aceasta este starea care este ușor pentru programatori să vadă și să înțeleagă; este starea care ar fi acolo dacă clasa ar fi doar un stil C struct.

la exterior, obiectele dvs. au utilizatori ai clasei dvs., iar acești utilizatori sunt limitați la utilizarea numai a funcțiilor de membru publicși a funcțiilor de membru friend. Acești utilizatori externi percep, de asemenea, obiectul ca având stare, de exemplu, dacă obiectul este de clasă Rectangle cu metode width(), height() și area(), utilizatorii dvs. ar spune că aceste trei fac parte din starea logică (sau abstractă sau înțeleaptă) a obiectului. Pentru un utilizator extern, obiectul Rectangle are de fapt o zonă, chiar dacă acea zonă este calculată din mers (de exemplu, dacă metoda area() returnează produsul lățimii și înălțimii obiectului). De fapt, și acesta este punctul important, utilizatorii dvs. nu știu și nu le pasă cumimplementați oricare dintre aceste metode; utilizatorii dvs. încă percep, din perspectiva lor, că obiectul dvs. are în mod logic o stare înțeleaptă de lățime, înălțime și zonă.

exemplul area() arată un caz în care starea logică poate conține elemente care nu sunt realizate direct în starea fizică. Opusul este, de asemenea, adevărat: clasele ascund uneori intenționat o parte din starea fizică(concretă, bit) a obiectelor lor de la utilizatori — în mod intenționat nu oferă funcții de membru public saufriendcare ar permite utilizatorilor să citească sau să scrie sau chiar să știe despre această stare ascunsă. Asta înseamnă că există biți în starea fizică a obiectului care nu au elemente corespunzătoare în starea logică a obiectului.

ca exemplu al acestui ultim caz, un obiect de colecție ar putea să-și memoreze ultima căutare în speranța de a îmbunătăți performanța următoarei sale căutări. Acest cache face cu siguranță parte din starea fizică a obiectului, dar există un detaliu intern de implementare care probabil nu va fi expus utilizatorilor — probabil că nu va face parte din starea logică a obiectului. A spune ce este ceea ce este ușor dacă gândiți din exterior-în: dacă utilizatorii obiectului de colecție nu au niciodatăpentru a verifica starea cache-ului în sine, atunci cache-ul este transparent și nu face parte din logica obiectului.

ar trebui ca Constanța funcțiilor mele de membru public să se bazeze pe ceea ce face metoda cu starea logică sau starea fizică a obiectului?

logic.

nu există nici o modalitate de a face această parte următoare ușor. O să doară. Cea mai bună recomandare este să stai jos. Și vă rog, pentru siguranța dvs., asigurați-vă că nu există unelte ascuțite în apropiere.

să ne întoarcem la exemplul colecție-obiect. Amintiți-vă: există o metodă de căutare thatcaches ultima căutare în speranța de a accelera căutări viitoare.

să precizăm ceea ce este probabil evident: să presupunem că metoda de căutare nu face nicio modificare la starea logică a obiectului de colectare.

deci … a venit timpul să te rănesc. Ești gata?

aici vine: în cazul în care metoda de căutare nu face nici o modificare la oricare dintre starea logică collection-obiect, dar itdoes schimba starea fizică collection-obiect (se face o schimbare foarte real cache foarte real), ar trebui thelookup metoda fi const?

răspunsul este un da răsunător. (Există excepții de la fiecare regulă, deci “da” ar trebui să aibă într-adevăr un asterisc lângă el,dar marea majoritate a timpului, răspunsul este da.)

este vorba despre “logic const” peste “fizic const.”Înseamnă că decizia de a decora ametoda cu const ar trebui să se bazeze în primul rând pe faptul dacă această metodă lasă starea logică neschimbată, indiferent(stai jos?) (s-ar putea să doriți să vă așezați) indiferent dacă metoda se întâmplă să facă schimbări foarte reale la starea fizică foarte reală a obiectului.

în cazul în care nu s-au scufundat în, sau în cazul în care nu sunt încă în durere, să-l tachineze în afară în două cazuri:

  • dacă o metodă schimbă orice parte a stării logice a obiectului, logic este un mutator; nu ar trebui să fie const evenif (așa cum se întâmplă de fapt!) metoda nu schimbă Niciun bit fizic al stării concrete a obiectului.
  • invers, o metodă este logic un inspector și ar trebui să fie const dacă nu schimbă niciodată nicio parte a stării logice a obiectului, chiar dacă (așa cum se întâmplă de fapt!) metoda modifică biții fizici ai stării concrete a obiectului.

dacă sunteți confuz, citiți-l din nou.

dacă nu ești confuz, dar ești supărat, bine: s-ar putea să nu-ți placă încă, dar cel puțin o înțelegi. Respirați adâncși repetați după mine: “constness a unei metode ar trebui să aibă sens din afara obiectului.”

dacă sunteți încă supărat, repetați acest lucru de trei ori: “Constanța unei metode trebuie să aibă sens pentru utilizatorii obiectului, iar acei utilizatori pot vedea doar starea logică a obiectului.”

dacă sunteți încă supărat, îmi pare rău, este ceea ce este. Suge – o și trăiește cu ea. Da, vor exista excepții; fiecare regulă le are. Dar, de regulă, în principiu, această noțiune logică const este bună pentru dvs. și bună pentru software-ul dvs.

încă un lucru. Acest lucru va deveni ciudat, dar să fim preciși dacă o metodă schimbă logica obiectului. Dacă sunteți în afara clasei — sunteți un utilizator normal, fiecare experiment pe care l-ați putea efectua (fiecare metodă sau consecință a metodelor pe care le apelați) ar avea aceleași rezultate (aceleași valori returnate, aceleași excepții sau lipsa excepțiilor), indiferent dacă ați apelat prima dată metoda de căutare. Dacă funcția de căutare a schimbat orice comportament viitor al oricărei metode viitoare (nu doar făcând — o mai rapidă, ci a schimbat rezultatul, a schimbat valoarea returnată, a schimbat excepția), atunci metoda de căutare a schimbat starea logică a obiectului-este un mutuator. Dar dacă metoda de căutarenu a schimbat nimic altceva decât să facă unele lucruri mai rapide, atunci este un inspector.

ce fac dacă vreau ca o funcție de membru const să facă o modificare “invizibilă” unui membru de date?

a se utiliza mutable (sau, în ultimă instanță, a se utiliza const_cast).

un procent mic de inspectori trebuie să facă modificări ale stării fizice a unui obiect care nu pot fi observate de utilizatorii externi — modificări ale stării fizice, dar nu logice.

de exemplu, obiectul de colecție discutat anterior a memorat în cache ultima sa Căutare în speranța îmbunătățirii performanței următoarei sale căutări. Deoarece memoria cache, în acest exemplu, nu poate fi observată direct de nicio parte a interfeței publice a obiectului de colecție (alta decât sincronizarea), existența și starea sa nu fac parte din starea logică a obiectului, astfel încât modificările aduse acestuia sunt invizibile pentru utilizatorii externi. Metoda de căutare este un inspector, deoarece nu schimbă niciodată starea logică a obiectului, indiferent de faptul că, cel puțin pentru implementarea actuală, modifică starea fizică a obiectului.

când metodele schimbă starea fizică, dar nu logică, metoda ar trebui în general marcată ca const deoarece este într-adevăr o metodă inspector. Asta creează o problemă: când compilatorul vede metoda const schimbând starea fizicăa obiectului this, se va plânge — va da codului dvs. un mesaj de eroare.

limbajul compilatorului c++ folosește cuvântul cheie mutable pentru a vă ajuta să îmbrățișați această noțiune logică const. În acest caz,ați marca memoria cache cu cuvântul cheie mutable, astfel compilatorul știe că este permis să se schimbe în interiorul unei metodeconst sau prin orice alt pointer sau referință const. În limbajul nostru, cuvântul cheie mutable marchează acele porțiuni ale stării fizice a obiectului care nu fac parte din starea logică.

cuvântul cheiemutable merge chiar înainte de declarația membrului de date, adică același loc în care ați putea pune const. Cealaltă abordare, care nu este preferată, este de a arunca const‘ness a indicatorului this, probabil prin intermediul cuvântului cheie const_cast :

Set* self = const_cast<Set*>(this); // See the NOTE below before doing this!

după această linie, self va avea aceiași biți ca this, adică self == this, dar self este un Set* mai degrabă decât unconst Set* (tehnic this este un const Set* const, dar cel mai potrivit const este irelevant pentru această discuție).Asta înseamnă că puteți utiliza self pentru a modifica obiectul indicat de this.

notă: există o eroare extrem de puțin probabilă care poate apărea cu const_cast. Se întâmplă numai atunci când trei lucruri foarte rare sunt combinate în același timp: un membru de date care ar trebui să fie mutable (cum este discutat mai sus), un compilercare nu acceptă cuvântul cheie mutable și/sau un programator care nu îl folosește și un obiect care a fost inițial definit ca fiind const (spre deosebire de un obiect normal, non-const care este indicat de un pointer-to-const).Deși această combinație este atât de rară încât s-ar putea să nu vi se întâmple niciodată, dacă s-a întâmplat vreodată, este posibil ca Codul să nu funcționeze (standardul spune că comportamentul este nedefinit).

dacă doriți vreodată să utilizați const_cast, utilizați mutable în schimb. Cu alte cuvinte, dacă vreodată trebuie să schimbați un membru al unui obiect și acel obiect este indicat printr-un pointer-to – const, cel mai sigur și mai simplu lucru de făcut este să adăugați mutable la declarația membrului. Puteți utiliza const_cast dacă sunteți sigur că obiectul real nu este const (de exemplu, dacă sunteți sigur că obiectul este declarat ceva de genul: Set s;), dar dacă obiectul în sine ar putea fi const (de exemplu, dacă ar putea fi declarat ca: const Set s;), utilizați mutablemai degrabă decât const_cast.

vă rugăm să nu scrieți spunând versiunea X a compilatorului Y pe mașină Z vă permite să schimbați un membru non-mutable al unui obiectconst. Nu-mi pasă — este ilegal în funcție de limba și Codul va eșua, probabil, pe un differentcompiler sau chiar o versiune diferită (un upgrade) de același compilator. Doar spune nu. Utilizați mutable în schimb. Scrie codcare este garantat să funcționeze, nu cod care nu pare să se rupă.

const_cast înseamnă oportunități de optimizare pierdute?

în teorie, da; în practică, nu.

chiar dacă limba a scos în afara legii const_cast, singura modalitate de a evita spălarea memoriei cache a registrului într-un apel const memberfunction ar fi rezolvarea problemei de aliasing (adică., pentru a dovedi că nu există indicii non-const care indicăla obiect). Acest lucru se poate întâmpla numai în cazuri rare (când obiectul este construit în domeniul de aplicare al invocării funcției de membru const și când toate invocările funcției de membru non-const dintre construcția obiectului și invocarea funcției de membruconst sunt legate static și când fiecare dintre aceste invocări este de asemenea inlined, și când constructorul însuși este inlined, iar când orice membru funcționează apelurile constructorului sunt inline).

de ce compilatorul îmi permite să schimb un int după ce l-am indicat cu un int const*?

deoarece “const int* p” înseamnă “p promite să nu schimbe *p,” nu “*p promite să nu se schimbe.”

cauzarea unui const int* pentru a indica un int nu const-ify int. int nu poate fi schimbat princonst int*, dar dacă altcineva are un int*(notă: nu const) care indică (“aliasuri”) același int, atunciint* poate fi folosit pentru a schimba int. De exemplu:

void f(const int* p1, int* p2){ int i = *p1; // Get the (original) value of *p1 *p2 = 7; // If p1 == p2, this will also change *p1 int j = *p1; // Get the (possibly new) value of *p1 if (i != j) { std::cout << "*p1 changed, but it didn't change via pointer p1!\n"; assert(p1 == p2); // This is the only way *p1 could be different }}int main(){ int x = 5; f(&x, &x); // This is perfectly legal (and even moral!) // ...}

rețineți că main() și f(const int*,int*) ar putea fi în diferite unități de compilare care sunt compilate în diferite zile ale săptămânii. În acest caz, nu există nici un fel compilatorul poate detecta, eventual, aliasing la momentul compilării. Prin urmare, nu există nicio modalitate de a face o regulă lingvistică care să interzică astfel de lucruri. De fapt, nici nu am vrea să facem o astfel de arule, deoarece, în general, este considerată o caracteristică pe care o puteți avea multe indicii care indică același lucru. Faptul că unul dintre acești indicatori promite să nu schimbe “lucrul” de bază este doar o promisiune făcută de pointer; nu este o promisiune făcută de “lucru”.

“const Fred * p” înseamnă că *p nu se poate schimba?

nu! (Acest lucru este legat de FAQ despre aliasing de int indicii.)

const Fred* p” înseamnă că Fred nu poate fi modificat prin pointer p, dar ar putea exista și alte modalități de a ajunge la obiect fără a trece printr-un const (cum ar fi un pointer non – const alias, cum ar fi un Fred*). De exemplu, dacă aveți două indicatoare ” const Fred* p “și” Fred* q ” care indică același obiect Fred (aliasing), pointer q poate fi folosit pentru a schimba obiectul Fred, dar pointer p nu poate.

class Fred {public: void inspect() const; // A const member function void mutate(); // A non-const member function};int main(){ Fred f; const Fred* p = &f; Fred* q = &f; p->inspect(); // Okay: No change to *p p->mutate(); // Error: Can't change *p via p q->inspect(); // Okay: q is allowed to inspect the object q->mutate(); // Okay: q is allowed to mutate the object f.inspect(); // Okay: f is allowed to inspect the object f.mutate(); // Okay: f is allowed to mutate the object // ...}

de Ce primesc o eroare de conversie a unui Foo** → const Foo**?

pentru că convertirea Foo** inox const Foo** ar fi nevalidă și periculoasă.

C++ permite conversia (în condiții de siguranță) Foo* Foo const*, dar dă o eroare dacă încercați să convertiți implicit Foo**const Foo**.

motivul pentru care această eroare este un lucru bun este prezentat mai jos. Dar mai întâi, aici este cea mai comună soluție: pur și simpluschimbați const Foo** la const Foo* const*:

class Foo { /* ... */ };void f(const Foo** p);void g(const Foo* const* p);int main(){ Foo** p = /*...*/; // ... f(p); // ERROR: it's illegal and immoral to convert Foo** to const Foo** g(p); // Okay: it's legal and moral to convert Foo** to const Foo* const* // ...}

motivul pentru care conversia de la Foo** la const Foo** este periculoasă este că vă va permite să modificați în tăcere și accidental un obiect const Foo fără o distribuție:

class Foo {public: void modify(); // make some modification to the this object};int main(){ const Foo x; Foo* p; const Foo** q = &p; // q now points to p; this is (fortunately!) an error *q = &x; // p now points to x p->modify(); // Ouch: modifies a const Foo!! // ...}

dacă linia q = &p ar fi legală, qar indica p. Următoarea linie, *q = &x, se modifică p în sine (deoarece *qeste p) pentru a indica x. Acesta ar fi un lucru rău, deoarece am fi pierdut calificativul const: p este un Foo*, darx este un const Foo. Linia p->modify()exploatează capacitatea pde a-și modifica referentul, care este adevărata problemă,deoarece am ajuns să modificăm un const Foo.

prin analogie, dacă ascundeți un criminal sub o deghizare legală, el poate exploata încrederea acordată acelei deghizări.Asta e rău.

din fericire, C++ vă împiedică să faceți acest lucru: linia q = &p este marcată de compilatorul C++ ca o eroare de compilare. Memento: vă rugăm să nu pointer-aruncat-ți de drum în jurul valorii de acel mesaj de eroare de compilare-timp. Spune Nu!

(Notă: există o asemănare conceptuală între aceasta și interdicția de a converti Derived**laBase**.)

Lasă un răspuns

Adresa ta de email nu va fi publicată.