Standard C++
- Const korrekthed
- Hvad er “const korrekthed”?
- Hvordan er “const korrekthed” relateret til almindelig type sikkerhed?
- skal jeg prøve at få tingene korrekt “før” eller “senere”?
- hvad betyder” const”?
- hvad er forskellen mellem” const* p”,” const p “og”const* const p”?
- hvad betyder” const& h”?
- hvad betyder “h const & h” og “H const* p”?
- giver “& const” nogen mening?
- Hvad er en “const medlemsfunktion”?
- Hvad er forholdet mellem en return-by-reference og en const-medlemsfunktion?
- Hvad er aftalen med “const-overbelastning”?
- Hvordan kan det hjælpe mig med at designe bedre klasser, hvis jeg skelner logisk tilstand fra fysisk tilstand?
- skal konstansen af mine offentlige medlemsfunktioner være baseret på, hvad metoden gør med objektets logiske tilstand eller fysiske tilstand?
- Hvad gør jeg, hvis jeg vil have en const-medlemsfunktion til at foretage en “usynlig” ændring af et datamedlem?
- betyder const_cast tabte optimeringsmuligheder?
- Hvorfor tillader kompilatoren mig at ændre en int, efter at jeg har peget på den med en const int*?
- betyder “const Fred* p”, at *p ikke kan ændre sig?
- Hvorfor får jeg en fejl ved konvertering af en Foo** reoler const Foo**?
Const korrekthed
Hvad er “const korrekthed”?
en god ting. Det betyder at bruge nøgleordet const
for at forhindre const
objekter i at blive muteret.
for eksempel, hvis du ønskede at oprette en funktion f()
der accepterede en std::string
, plus du vil love opkalderikke at ændre opkalderens std::string
der bliver overført til f()
, kan du have f()
modtage sin std::string
parameter…
-
void f1(const std::string& s);
// Pass ved henvisning-til-const
-
void f2(const std::string* sptr);
// Pass af pointer-to-const
-
void f3(std::string s);
// Pass by value
i pass by reference-to – const
og pass by pointer-to-const
sager, ethvert forsøg på at ændre opkalderensstd::string
inden for f()
funktionerne vil blive markeret af kompilatoren som en fejl ved kompileringstid. Denne kontrol udføres helt ved kompileringstid: der er ingen kørselstid eller hastighedsomkostninger for const
. I tilfælde af pass by value (f3()
) får den kaldte funktion en kopi af opkalderens std::string
. Dette betyder, at f3()
kan ændre sin lokale kopi, men kopien ødelægges, når f3()
vender tilbage. Især f3()
kan ikke ændre opkalderens std::string
objekt.
Antag som et modsat eksempel, at du ville oprette en funktion g()
, der accepterede en std::string
, men du vil lade opkaldere vide, at g()
muligvis ændrer opkalderens std::string
objekt. I dette tilfælde kan du have g()
modtage sinstd::string
parameter…
-
void g1(std::string& s);
// Pass ved henvisning til ikke-const
-
void g2(std::string* sptr);
// Pass af pointer-to-non-const
manglen på const
i disse funktioner fortæller kompilatoren, at de har lov til (men ikke er forpligtet til) at ændre thecaller ‘ s std::string
objekt. Således kan de videregive deres std::string
til en hvilken som helst af f()
funktionerne, men kun f3()
(den, der modtager sin parameter “efter værdi”) kan passere dens std::string
til g1()
eller g2()
. Hvis f1()
eller f2()
skal ringe til enten g()
funktionen, skal en lokal kopi af std::string
objektet sendes til g()
funktionen; theparameter til f1()
eller f2()
kan ikke sendes direkte til enten g()
funktionen. F. eks.,
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}
naturligvis i ovenstående tilfælde foretages eventuelle ændringer, som g1()
foretager, til localCopy
objektet, der er lokalt til f1()
. især vil der ikke blive foretaget ændringer i parameteren const
, der blev overført med henvisning til f1()
.
Hvordan er “const korrekthed” relateret til almindelig type sikkerhed?
at erklære const
-ness af en parameter er bare en anden form for typesikkerhed.
hvis du finder almindelig type sikkerhed hjælper dig med at få systemer korrekt (det gør det, især i store systemer), finder duconst
korrekthed hjælper også.
fordelen ved const
korrekthed er, at det forhindrer dig i utilsigtet at ændre noget, du ikke forventedeville blive ændret. Du ender med at skulle dekorere din kode med et par ekstra tastetryk (nøgleordet const
) medfordel, at du fortæller kompilatoren og andre programmører noget ekstra stykke vigtig semantisk information— information, som kompilatoren bruger til at forhindre fejl og andre programmører bruger som dokumentation.
konceptuelt kan du forestille dig, at const std::string
for eksempel er en anden klasse end almindeligstd::string
, da varianten const
konceptuelt mangler de forskellige mutative operationer,der er tilgængelige i varianten ikke- const
. For eksempel kan du konceptuelt forestille dig, at en const std::string
simpelthen ikke har en opgaveoperatør+=
eller andre mutative operationer.
skal jeg prøve at få tingene korrekt “før” eller “senere”?
helt, meget, meget begyndelsen.
Back-patching const
korrekthed resulterer i en sneboldeffekt: hver const
du tilføjer “herovre” kræver fire mereat blive tilføjet “derovre.”
Tilføj const
tidligt og ofte.
hvad betyder” const”?
det betyder p
peger på et objekt af klasse X
, men p
kan ikke bruges til at ændre det X
objekt (naturligvis p
kunne også være NULL
).
Læs det højre mod venstre: “p er en markør til en H, der er konstant.”
for eksempel, hvis klasse X
har en const
medlemsfunktion som inspect() const
, er det okay at sigep->inspect()
. Men hvis klasse X
har en ikke – const
medlemsfunktion kaldet mutate()
, er det en fejl, hvis du siger p->mutate()
.
signifikant er denne fejl fanget af kompilatoren ved kompileringstid-ingen run — time test udføres. Det betyder, at const
ikke sænker dit program og kræver ikke, at du skriver Ekstra testcases for at kontrollere ting ved kørsel-thecompiler gør arbejdet på kompileringstidspunktet.
hvad er forskellen mellem” const* p”,” const p “og”const* const p”?
Læs markørerklæringerne fra højre mod venstre.
-
const X* p
betyder ”p
peger på enX
det erconst
“:X
objektet kan ikke ændres viap
. -
X* const p
betyder “p
er enconst
pointer til enX
det er ikke –const
“: du kan ikke ændre markørenp
selv, men du kan ændreX
objektet viap
. -
const X* const p
betyder “p
er enconst
peger til enX
det erconst
“: du kan ikke ændre markørenp
selv, og du kan heller ikke ændreX
objektet viap
.
og åh ja, nævnte jeg at læse dine markørerklæringer højre mod venstre?
hvad betyder” const& h”?
det betyder x
aliaser et X
objekt, men du kan ikke ændre det X
objekt via x
.
Læs det højre mod venstre: “x
er en henvisning til en X
, der er const
.”
for eksempel, hvis klasse X
har en const
medlemsfunktion som inspect() const
, er det okay at sigex.inspect()
. Men hvis klasse X
har en ikke – const
medlemsfunktion kaldet mutate()
, er det en fejlhvis du siger x.mutate()
.
dette er helt symmetrisk med pointers til const, herunder det faktum, at kompilatoren gør al kontrol på kompileringstid, hvilket betyder, at const
ikke sænker dit program og kræver ikke, at du skriver Ekstra testsager for at kontrollere ting ved kørsel.
hvad betyder “h const & h” og “H const* p”?
X const& x
svarer til const X& x
, og X const* x
svarer tilconst X* x
.
nogle mennesker foretrækker const
-til-højre-stilen og kalder den “konsistent const
” eller ved hjælp af et udtryk, der er opfundet af Simon Brand, “øst const
.”Faktisk” øst const
” stil kan være mere konsekvent end alternativet:” øst const
” stil altid sætter const
til højre for, hvad det constifies, mens den anden stil undertiden sætter const
til venstreog nogle gange til højre (for const
pointer erklæringer og const
medlemsfunktioner).
med stilen “øst const
” defineres en lokal variabel, der er const
, med const
til højre:int const a = 42;
. Tilsvarende er en static
variabel, der er const
, defineret som static double const x = 3.14;
.Dybest set hver const
ender til højre for den ting, den constifies, herunder const
, der skal være til højre: const
pointererklæringer og med en const
medlemsfunktion.
stilen “øst const
” er også mindre forvirrende, når den bruges med typealiaser: hvorfor har foo
og bar
forskellige typer her?
using X_ptr = X*;const X_ptr foo;const X* bar;
brug af “Øst const
” stil gør dette klarere:
using X_ptr = X*;X_ptr const foo;X* const foobar;X const* bar;
det er tydeligere her, at foo
og foobar
er af samme type, og at bar
er en anden type.
“øst const
” – stilen er også mere konsistent med markørerklæringer. Kontrast den traditionelle stil:
const X** foo;const X* const* bar;const X* const* const baz;
med” øst const
” – stilen
X const** foo;X const* const* bar;X const* const* const baz;
på trods af disse fordele er const
-til-højre-stilen endnu ikke populær, så legacy code har tendens til at have den traditionelle stil.
giver “& const” nogen mening?
Nej, Det er noget vrøvl.
for at finde ud af, hvad ovenstående erklæring betyder, skal du læse den højre mod venstre: “x
er en const
henvisning til en X
“. Men det er overflødigt — referencer er altid const
, i den forstand at du aldrig kan genoptage areference for at få det til at henvise til et andet objekt. Aldrig. Med eller uden const
.
med andre ord svarer “X& const x
” funktionelt til “X& x
“. Da du ikke vinder noget ved at tilføjeconst
efter &
, bør du ikke tilføje det: det vil forvirre folk — const
vil få nogle til at tro det X
er const
, som om du havde sagt “const X& x
“.
Hvad er en “const medlemsfunktion”?
en medlemsfunktion, der inspicerer (snarere end muterer) dens objekt.
a const
medlemsfunktionen er angivet med et const
suffiks lige efter medlemsfunktionens parameterliste. Medlemsfunktioner med et suffiks const
kaldes “const
medlemsfunktioner “eller” inspektører.”Medlemsfunktioner uden et suffiksconst
kaldes” ikke – const
medlemsfunktioner “eller” mutatorer.”
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}
forsøget på at ringe unchangeable.mutate()
er en fejl fanget på kompileringstidspunktet. Der er ingen runtime space eller speedpenalty for const
, og du behøver ikke at skrive testcases for at kontrollere det ved runtime.
den efterfølgende const
på inspect()
medlemsfunktion skal bruges til at betyde, at metoden ikke ændrer objektets abstract (client-synlig) tilstand. Det er lidt anderledes end at sige, at metoden ikke vil ændre de “rå bits” afobjektets struct
. C++ – kompilatorer må ikke tage den “bitvise” fortolkning, medmindre de kan løse aliasing-problemet, som normalt ikke kan løses (dvs.et alias, der ikke erconst
, kunne eksistere, hvilket kunne ændre objektets tilstand). En anden (vigtig) indsigt fra dette aliasing-problem: at pege på et objekt med en pointer-to-const
garanterer ikke, at objektet ikke ændres; det lover blot, at objektet ikke ændres via denne pointer.
Hvad er forholdet mellem en return-by-reference og en const-medlemsfunktion?
hvis du vil returnere et medlem af dit this
objekt ved henvisning fra en inspektørmetode, skal du returnere det ved hjælp af reference-to-const (const X& inspect() const
) eller efter værdi (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!!}
den gode nyhed er, at kompilatoren ofte vil fange dig, hvis du får det forkert. Især hvis du ved et uheld returnerer et medlem af ditthis
objekt ved ikke-const
reference, som iPerson::name_evil()
ovenfor, vil kompilatoren ofte opdage det og give dig en kompileringstidsfejl, mens du kompilerer indersiden af, i dette tilfælde Person::name_evil()
.
den dårlige nyhed er, at kompilatoren ikke altid fanger dig: der er nogle tilfælde, hvor kompilatoren simpelthen ikke nogensinde vil give dig en kompileringstidsfejlmeddelelse.
Oversættelse: du skal tænke. Hvis det skræmmer dig, skal du finde en anden arbejdslinje; “tænk” er ikke et ord på fire bogstaver.
husk “const
filosofi” spredt i hele dette afsnit: en const
medlemsfunktion må ikke ændre (eller tillade en opkalder at ændre) this
objektets logiske tilstand (AKA abstrakt tilstand AKA betydning). Tænk på, hvad et objekt betyder, ikke hvordan det implementeres internt. En persons alder og navn er logiskdel af personen, men personens nabo og arbejdsgiver er det ikke. En inspektørmetode,der returnerer en del af this
objektets logiske / abstrakte / meningsvise tilstand, må ikke returnere en ikke-const
markør (eller reference) til den del, uafhængigt af om den del internt implementeres som et direkte data-medlem fysisk indlejret ithis
objektet eller på anden måde.
Hvad er aftalen med “const-overbelastning”?
const
overbelastning hjælper dig med at opnå const
korrekthed.
const
overbelastning er, når du har en inspektørmetode og en mutatormetodemed samme navn og samme antal og typer parametre. De to forskellige metoder adskiller sig kun ved, atinspektøren er const
og mutatoren er ikke-const
.
den mest almindelige anvendelse af const
overbelastning er hos abonnentoperatøren. Du bør generelt forsøge at bruge en afstandardcontainerskabeloner, såsom std::vector
, men hvis du har brug for at oprette din egen klasse, der har en abonnentoperator, her er tommelfingerreglen: abonnentoperatører kommer ofte parvis.
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 // ...};
abonnentoperatøren const
returnerer en const
-reference, så kompilatoren forhindrer opkaldere i utilsigtet at ændre Fred
. Operatøren non – const
subscript returnerer en non – const
reference, som er din måde at fortælle dine opkaldere (og kompilatoren), at dine opkaldere har lov til at ændre Fred
objektet.
når en bruger af din MyFredList
klasse kalder abonnentoperatøren, vælger kompilatoren, hvilken overbelastning der skal kaldes basedon konstansen af deres MyFredList
. Hvis den, der ringer op, har en MyFredList a
eller MyFredList& a
, vil a
ringeden ikke-const
abonnentoperatør, og den, der ringer op, ender med en ikke – const
henvisning til en Fred
:
Antag for eksempel class Fred
har en inspektør-metode inspect() const
og en mutator-metode 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}
men hvis den, der ringer op, har en const MyFredList a
eller const MyFredList& a
, ringer a
til abonnentoperatoren const
, og den, der ringer op, ender med en const
henvisning til en Fred
. Dette gør det muligt for den, der ringer op, at inspicere Fred
på a
, men det forhindrer, at den, der ringer op, utilsigtet muterer/ændrer Fred
på 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 overbelastning for abonnement – og funcall-operatører er illustreret her,her, her, her og her.
du kan selvfølgelig også bruge const
-overbelastning til andre ting end abonnentoperatøren.
Hvordan kan det hjælpe mig med at designe bedre klasser, hvis jeg skelner logisk tilstand fra fysisk tilstand?
fordi det opfordrer dig til at designe dine klasser udefra-ind snarere end indefra-ud, hvilket igen gør dine klasser og objekter lettere at forstå og bruge, mere intuitive, mindre fejlbehæftede og hurtigere. (Okay, det er en lille overforenkling. For at forstå alle if ‘erne og men’ erne skal du bare læse resten af dettesvar!)
lad os forstå dette indefra og ud-du vil (skulle) designe dine klasser fraudenfor — in, men hvis du er ny til dette koncept, er det lettere at forstå fraindvendigt ud.
på indersiden har dine objekter fysisk (eller konkret eller bitvis) tilstand. Dette er den tilstand, der er let for programmererat se og forstå; det er staten, der ville være der, hvis klassen bare var en C-stil struct
.
på ydersiden har dine objekter brugere af din klasse, og disse brugere er begrænset til kun at bruge public
medlemsfunktioner og friend
s. Disse eksterne brugere opfatter også objektet som havende tilstand, for eksempel hvis objektet er af klasse Rectangle
med metoder width()
, height()
og area()
, vil dine brugere sige, at disse treer alle en del af objektets logiske (eller abstrakte eller meningsfulde) tilstand. Til en ekstern bruger har Rectangle
– objektet faktisk et område, selvom dette område beregnes undervejs (f.eks. hvis area()
– metoden returnerer produktet af objektets bredde og højde). Faktisk, og dette er det vigtige punkt, kender dine brugere ikke Og er ligeglad med, hvordan duimplementere nogen af disse metoder; dine brugere stadig opfatter, fra deres perspektiv, at dit objekt logisk har ammeningvis tilstand af bredde, højde, og område.
area()
eksemplet viser et tilfælde, hvor den logiske tilstand kan indeholde elementer, der ikke direkte realiseres ifysisk tilstand. Det modsatte er også sandt: klasser skjuler undertiden bevidst en del af deres objekters fysiske(konkrete, bitvise) tilstand for brugerne — de giver med vilje ingen public
medlemsfunktioner ellerfriend
s, der giver brugerne mulighed for at læse eller skrive eller endda vide om denne skjulte tilstand. Det betyder, at der er bits i objektets fysiske tilstand, der ikke har nogen tilsvarende elementer i objektets logiske tilstand.
som et eksempel på sidstnævnte tilfælde kan et samlingsobjekt muligvis cache sit sidste opslag i håb om at forbedre ydeevnen for dets næste opslag. Denne cache er bestemt en del af objektets fysiske tilstand, men der er det en internimplementeringsdetalje, der sandsynligvis ikke vil blive udsat for brugere — det vil sandsynligvis ikke være en del af objektets logiske tilstand. At fortælle, hvad der er, hvad der er let, hvis du tænker udefra-ind: hvis indsamlingsobjektets brugere har nunår at kontrollere tilstanden af cachen selv, så er cachen gennemsigtig og er ikke en del af objektets logisketilstand.
skal konstansen af mine offentlige medlemsfunktioner være baseret på, hvad metoden gør med objektets logiske tilstand eller fysiske tilstand?
logisk.
der er ingen måde at gøre denne næste del let. Det kommer til at gøre ondt. Bedste anbefaling er at sætte sig ned. Og vær venlig, for din sikkerhed, sørg for, at der ikke er skarpe redskaber i nærheden.
lad os gå tilbage til indsamlingsobjekteksemplet. Huske: der er en opslagsmetode, der henter det sidste opslag i håb om at fremskynde fremtidige opslag.
lad os angive, hvad der sandsynligvis er indlysende: Antag, at opslagsmetoden ikke ændrer nogen af thecollection-objektets logiske tilstand.
så… tiden er inde til at skade dig. Er du klar?
her kommer: hvis opslagsmetoden ikke ændrer nogen af samlingsobjektets logiske tilstand, men det ændrer samlingsobjektets fysiske tilstand (det gør en meget reel ændring til den meget rigtige cache), skal opslagsmetoden være const
?
svaret er et rungende ja. (Der er undtagelser fra enhver regel, så “ja” burde virkelig have en stjerne ved siden af,men langt størstedelen af tiden er svaret ja.)
dette handler om “logisk const
” over “fysisk const
.”Det betyder, at beslutningen om, hvorvidt man skal dekorere amethod med const
, primært skal afhænge af, om denne metode efterlader den logiske tilstand uændret, uanset(sidder du ned?) (du vil måske sætte dig ned) uanset om metoden sker for at gøre meget realændringer til objektets meget reelle fysiske tilstand.
i tilfælde af at det ikke synker ind, eller hvis du endnu ikke har smerter, lad os drille det fra hinanden i to tilfælde:
- hvis en metode ændrer nogen del af objektets logiske tilstand, er det logisk en mutator; det bør ikke være
const
evenif (som faktisk sker!) metoden ændrer ikke nogen fysiske bits af objektets konkrete tilstand. - omvendt er en metode logisk en inspektør og bør være
const
hvis den aldrig ændrer nogen del af objektets logiske tilstand, selvom (som faktisk sker!) metoden ændrer fysiske bits af objektets konkrete tilstand.
hvis du er forvirret, skal du læse den igen.
hvis du ikke er forvirret, men er vred, god: du kan måske ikke lide det endnu, men i det mindste forstår du det. Tag en dyb indånding og gentag efter mig: “const
ness af en metode skal give mening uden for objektet.”
hvis du stadig er vred, skal du gentage dette tre gange: “konstansen af en metode skal give mening for objektets brugere, og disse brugere kan kun se objektets logiske tilstand.”
hvis du stadig er vred, undskyld, det er hvad det er. Sug det op og leve med det. Ja, der vil være undtagelser; hver regel har dem. Men som hovedregel er dette logiske const
begreb godt for dig og godt for dit program.
en ting mere. Dette kommer til at blive inane, men lad os være præcise om, hvorvidt en metode ændrer objektets logisketilstand. Hvis du er uden for klassen – du er en normal bruger, ville hvert eksperiment, du kunne udføre (hver metode eller sekvens af metoder, du ringer til) have de samme resultater (samme returværdier, samme undtagelser eller manglende undtagelser), uanset om du først kaldte den opslagsmetode. Hvis opslagsfunktionen ændrede enhver fremtidig opførsel af enhver fremtidig metode (ikke bare gør det hurtigere, men ændrede resultatet, ændrede returværdien, ændrede undtagelse), så ændrede opslagsmetoden objektets logiske tilstand — det er en mutuator. Men hvis opslagsmetodenændrede intet andet end måske at gøre nogle ting hurtigere, så er det en inspektør.
Hvad gør jeg, hvis jeg vil have en const-medlemsfunktion til at foretage en “usynlig” ændring af et datamedlem?
brug mutable
(eller som en sidste udvej, Brug const_cast
).
en lille procentdel af inspektører skal foretage ændringer i et objekts fysiske tilstand, som ikke kan observeres af eksternbrugere — ændringer i den fysiske, men ikke logiske tilstand.
for eksempel cachelagrede samlingsobjektet tidligere sit sidste opslag i håb omforbedring af ydeevnen for dets næste opslag. Da cachen i dette eksempel ikke kan observeres direkte af nogen delaf indsamlingsobjektets offentlige grænseflade (bortset fra timing), er dens eksistens og tilstand ikke en del af objektets logiske tilstand, så ændringer i den er usynlige for eksterne brugere. Opslagsmetoden er en inspektør, da den aldrig ændrer objektets logiske tilstand, uanset det faktum, at det i det mindste for den nuværende implementering ændrer objektets fysiske tilstand.
når metoder ændrer den fysiske, men ikke logiske tilstand, skal metoden generelt markeres som const
da den virkelig er en inspektør-metode. Det skaber et problem: når kompilatoren ser din const
metode, der ændrer den fysiske tilstandaf this
objektet, vil det klage — det vil give din kode en fejlmeddelelse.
C++ compiler-sproget bruger nøgleordet mutable
til at hjælpe dig med at omfavne denne logiske const
forestilling. I dette tilfælde markerer du cachen med nøgleordet mutable
, på den måde ved kompilatoren, at det er tilladt at ændre sig i enconst
metode eller via en anden const
markør eller reference. I vores lingo markerer nøgleordet
nøgleordetmutable
går lige før datamedlemets erklæring, det vil sige det samme sted, hvor du kunne placere const
. Den anden tilgang, ikke foretrukket, er at kaste const
‘ness af this
markøren, sandsynligvis via const_cast
nøgleordet:
Set* self = const_cast<Set*>(this); // See the NOTE below before doing this!
efter denne linje vil self
have de samme bits som this
, det vil sige self == this
, men self
er en Set*
snarere end enconst Set*
(teknisk this
er en const Set* const
, men den rigtige const
er irrelevant for denne diskussion).Det betyder, at du kan bruge self
til at ændre objektet peget på af this
.
Bemærk: Der er en yderst usandsynlig fejl, der kan opstå med const_cast
. Det sker kun, når tre meget sjældneting kombineres på samme tid: et datamedlem, der burde være mutable
(som beskrevet ovenfor), en kompilator, der ikke understøtter nøgleordet mutable
og/eller en programmør, der ikke bruger det, og et objekt, der oprindeligt var defineret til at være const
(i modsætning til et normalt, ikke-const
objekt, der peges på af en pointer-to-const
).Selvom denne kombination er så sjælden, at den måske aldrig sker for dig, hvis den nogensinde skete, fungerer koden muligvis ikke (theStandard siger, at adfærden er udefineret).
hvis du nogensinde vil bruge const_cast
, skal du bruge mutable
i stedet. Med andre ord, hvis du nogensinde har brug for at ændre et medlem af anobject, og det objekt peges på af en pointer-to-const
, er den sikreste og enkleste ting at gøre at tilføje mutable
tilmedlemets erklæring. Du kan bruge const_cast
, hvis du er sikker på, at det faktiske objekt ikke er const
(f.eks. hvis du er sikker på, at objektet er erklæret noget som dette: Set
s;
), men hvis selve objektet måske er const
(f. eks. hvis det kan erklæres som: const Set s;
), skal du bruge mutable
i stedet for const_cast
.
skriv venligst ikke at sige version af compiler Y på maskine Å giver dig mulighed for at ændre et ikke-mutable
medlem af et const
objekt. Jeg er ligeglad – det er ulovligt i henhold til sproget, og din kode vil sandsynligvis mislykkes på en differentcompiler eller endda en anden version (en opgradering) af den samme compiler. Bare sig nej. Brug mutable
i stedet. Skriv kodeDet er garanteret at arbejde, ikke kode, der ikke synes at bryde.
betyder const_cast tabte optimeringsmuligheder?
i teorien ja; i praksis Nej.
selvom sproget er forbudt const_cast
, ville den eneste måde at undgå at skylle registercachen over et const
medlemsfunktionsopkald være at løse aliasingproblemet (dvs., for at bevise, at der ikke er nogen ikke-const
pointers, der pegertil objektet). Dette kan kun ske i sjældne tilfælde (når objektet er konstrueret inden for rammerne af const
medlemsfunktionens påkaldelse, og når alle ikke-const
medlemsfunktionens påkaldelser mellem objektets konstruktion ogconst
medlemsfunktionens påkaldelse er statisk bundet, og når hver eneste af disse påkaldelser også er inline
d, og når konstruktøren selv er inline
d, og når et medlemsfunktioner konstruktøren kalder er inline
).
Hvorfor tillader kompilatoren mig at ændre en int, efter at jeg har peget på den med en const int*?
fordi “const int* p
” betyder “p
lover ikke at ændre *p
,” ikke “*p
lover ikke at ændre sig.”
forårsager en const int*
at pege på en int
ikke const
-ify int
. int
kan ikke ændres viaconst int*
, men hvis en anden har en int*
(Bemærk: Nej const
), der peger på (“aliaser”) det samme int
, så kanint*
bruges til at ændre int
. Eksempel:
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!) // ...}
Bemærk, at main()
og f(const int*,int*)
kunne være i forskellige kompileringsenheder, der udarbejdes på forskellige dageaf ugen. I så fald er der ingen måde, at kompilatoren muligvis kan registrere aliasing på kompileringstidspunktet. Derfor thereis ingen måde, vi kunne gøre et sprog regel, der forbyder denne slags ting. Faktisk ville vi ikke engang gerne lave en sådanregel, da det generelt betragtes som en funktion, at du kan have mange pointers, der peger på det samme. Det faktum, at en af disse pointers lover ikke at ændre den underliggende “ting” er bare et løfte fra markøren; det er ikke et løfte fra “tinget”.
betyder “const Fred* p”, at *p ikke kan ændre sig?
Nej! (Dette er relateret til Ofte stillede spørgsmål om aliasing af int
pointers.)
“const Fred* p
” betyder, at Fred
ikke kan ændres via pointer p
, men der kan være andre måder at komme påobject uden at gå gennem en const
(såsom en alias ikke-const
pointer som en Fred*
). Hvis du f.eks. har to pointere “const Fred* p
” og “Fred* q
“, der peger på det samme Fred
objekt (aliasing), kan pointer q
bruges til at ændre Fred
objektet, men pointer p
kan ikke.
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 // ...}
Hvorfor får jeg en fejl ved konvertering af en Foo** reoler const Foo**?
fordi konvertering Foo**
let const Foo**
ville være ugyldig og farlig.
C++ tillader (sikker) konvertering Foo*
list Foo const*
, men giver en fejl, hvis du forsøger at implicit konvertereFoo**
list const Foo**
.
begrundelsen for, hvorfor denne fejl er en god ting, er angivet nedenfor. Men først, her er den mest almindelige løsning: simplychange const Foo**
til 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* // ...}
årsagen til konverteringen fra Foo**
liter const Foo**
er farlig er, at det ville lade dig stille og uheldigt ændre et const Foo
objekt uden en cast:
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!! // ...}
hvis q = &p
linjen var lovlig, ville q
pege på p
. Den næste linje, *q = &x
, ændrer p
sig selv (siden *q
er p
) for at pege på x
. Det ville være en dårlig ting, da vi ville have mistet const
kvalifikationen: p
er en Foo*
menx
er en const Foo
. Linjen p->modify()
udnytter p
‘ s evne til at ændre sin referent, hvilket er det virkelige problem,da vi endte med at ændre en const Foo
.
analogt, hvis du skjuler en kriminel under en lovlig forklædning, kan han derefter udnytte den tillid, der er givet til denne forklædning.Det er slemt.
heldigvis forhindrer C++ dig i at gøre dette: linjen q = &p
er markeret af C++-kompilatoren som en kompileringstidsfejl. Påmindelse: venligst ikke pointer-cast din vej rundt at kompilere-tid fejlmeddelelse. Bare Sig Nej!
(Bemærk: Der er en konceptuel lighed mellem dette og forbuddet mod at konvertereDerived**
til Base**
.)