Standaard C++
- Const correctheid
- Wat is “const correctheid”?
- hoe verhoudt “const correctheid” zich tot gewone veiligheid?
- moet ik proberen om dingen correct te krijgen “vroeger” of “later”?
- wat betekent “const X * p”?
- Wat is het verschil tussen “const X* p”, “X* const p” en “const X* const p”?
- wat betekent “const X& x”?
- Wat betekenen” X const& x” en “X const* p”?
- slaat “X& const x” ergens op?
- Wat is een “const member function”?
- Wat is de relatie tussen een return-by-reference en een const member function?
- hoe zit het met “const-overloading”?
- Hoe kan het me helpen betere klassen te ontwerpen als ik logische toestand van fysieke toestand onderscheid?
- moet de Consten van mijn publieke member-functies gebaseerd zijn op wat de methode doet met de logische of fysieke toestand van het object?
- Wat moet ik doen als Ik wil dat een const-member-functie een “onzichtbare” wijziging maakt in een datalid?
- betekent const_cast gemiste optimalisatiemogelijkheden?
- Waarom staat de compiler me toe om een int te wijzigen nadat ik er met een const int*naar heb gewezen?
- betekent” const Fred* p ” dat * p niet kan veranderen?
- Waarom krijg ik een fout bij het converteren van een Foo* * → const Foo**?
Const correctheid
Wat is “const correctheid”?
een goede zaak. Het betekent het gebruik van het trefwoord const
om te voorkomen dat const
objecten worden gemuteerd.
bijvoorbeeld, als je wilde om een functie f()
dat aanvaard std::string
, plus u wilt belofte callersnot wijzigen van het nummer van de beller std::string
dat wordt doorgegeven aan f()
u kunt f()
ontvangen std::string
parameter…
-
void f1(const std::string& s);
// langs referentie–const
-
void f2(const std::string* sptr);
// Pas door de aanwijzer naar-const
-
void f3(std::string s);
// Pass waarde
In het doorgeven door verwijzing-naar-const
en langs een pointer-naar-const
gevallen, alle pogingen tot het wijzigen van de bellerstd::string
in de f()
functies zouden worden gemarkeerd door de compiler een fout op compileertijd. Deze controle wordt volledig uitgevoerd tijdens het compileren: er zijn geen run-time ruimte of snelheid kosten voor de const
. In het geval pass by value (f3()
) krijgt de aangeroepen functie een kopie van std::string
van de beller. Dit betekent dat f3()
zijn localcopy kan wijzigen, maar de kopie wordt vernietigd wanneer f3()
terugkeert. In het bijzonder kan f3()
het std::string
object van de beller niet wijzigen.
stel dat u een functie g()
wilt aanmaken die een std::string
accepteert, maar dat u callers wilt laten weten dat g()
het std::string
object van de beller kan veranderen. In dit geval kunt u g()
de parameterstd::string
laten ontvangen…
-
void g1(std::string& s);
// passeer door verwijzing-naar-non-const
-
void g2(std::string* sptr);
// passeer pointer-to-non-const
het ontbreken van const
in deze functies vertelt de compiler dat ze het std::string
object van thecaller mogen (maar niet verplicht zijn) wijzigen. Zo kunnen ze hun std::string
doorgeven aan een van de f()
functies, maar alleen f3()
(degene die de parameter “op waarde” ontvangt) kan zijn std::string
doorgeven aan g1()
of g2()
. Als f1()
of f2()
een g()
functie moet aanroepen, moet een lokale kopie van het std::string
object worden doorgegeven aan de g()
functie; deparameter aan f1()
of f2()
kan niet direct worden doorgegeven aan een van beide g()
functies. Bijv..,
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}
natuurlijk worden in het bovenstaande geval alle wijzigingen die g1()
aanbrengt aangebracht in het localCopy
object dat lokaal is tot f1()
.in het bijzonder zullen geen wijzigingen worden aangebracht in de const
parameter die werd doorgegeven door verwijzing naar f1()
.
hoe verhoudt “const correctheid” zich tot gewone veiligheid?
declareren van de const
-heid van een parameter is gewoon een andere vorm van typeveiligheid.
als u vindt dat gewone type veiligheid U helpt om systemen correct te krijgen (dat doet het; vooral in grote systemen), zult u ookconst
correctheid helpen.
het voordeel van const
correctheid is dat het voorkomt dat u per ongeluk iets wijzigt waarvan u niet verwachtte dat het zou worden gewijzigd. Uiteindelijk moet je je code versieren met een paar extra toetsaanslagen (het const
sleutelwoord), met het voordeel dat je de compiler en andere programmeurs een extra stuk belangrijke semantische informatie vertelt— informatie die de compiler gebruikt om fouten te voorkomen en andere programmeurs gebruiken als documentatie.
conceptueel kunt u zich voorstellen dat const std::string
bijvoorbeeld een andere klasse is dan gewone std::string
, omdat de const
variant conceptueel de verschillende mutatieve operaties mist die beschikbaar zijn in de niet-const
variant. U kunt zich bijvoorbeeld conceptueel voorstellen dat een const std::string
eenvoudigweg geen toewijzings-operator+=
of andere mutatieve operaties heeft.
moet ik proberen om dingen correct te krijgen “vroeger” of “later”?
aan het zeer, zeer, zeer begin.
Back-patching const
correctheid resulteert in een sneeuwbaleffect: elke const
die u toevoegt “hier” vereist vier moreto worden toegevoegd “daar.”
voeg const
vroeg en vaak toe.
wat betekent “const X * p”?
het betekent p
wijst naar een object van klasse X
, maar p
kan niet worden gebruikt om dat X
object te veranderen (natuurlijk kan p
ook NULL
zijn).
lees het van rechts naar links: “p is een aanwijzer naar een X die constant is.”
bijvoorbeeld, als klasse X
een const
member functie heeft zoals inspect() const
, is het goed omp->inspect()
te zeggen. Maar als klasse X
een niet – const
member functie heeft genaamd mutate()
, is het een fout als je p->mutate()
zegt.
significant, deze fout wordt opgevangen door de compiler tijdens het compileren-er worden geen run — time tests gedaan. Dat betekent dat const
uw programma niet vertraagt en dat u geen extra testcases hoeft te schrijven om dingen tijdens runtime te controleren-de compiler doet het werk tijdens het compileren.
Wat is het verschil tussen “const X* p”, “X* const p” en “const X* const p”?
lees de verwijzingen van rechts naar links.
-
const X* p
betekent “p
wijst naar eenX
dieconst
is”: hetX
object kan niet worden gewijzigd viap
. -
X* const p
betekent “p
is eenconst
pointer naar eenX
die niet –const
is”: u kunt de aanwijzerp
zelf niet wijzigen, maar u kunt hetX
object wijzigen viap
. -
const X* const p
betekent “p
is eenconst
pointer naar eenX
dieconst
is”: U kunt de pointerp
zelf niet wijzigen, noch kunt u hetX
object wijzigen viap
.
en, oh ja, had ik al gezegd dat ik uw verwijzingen van rechts naar links moest lezen?
wat betekent “const X& x”?
het betekent x
aliassen een X
object, maar u kunt dat X
object niet wijzigen via x
.
lees het van rechts naar links: “x
is een verwijzing naar een X
die const
is.”
bijvoorbeeld, als klasse X
een const
member functie heeft zoals inspect() const
, is het goed omx.inspect()
te zeggen. Maar als klasse X
een niet-const
member functie genaamd mutate()
heeft, is het een errorif als je x.mutate()
zegt.
dit is geheel symmetrisch met verwijzingen naar const, inclusief het feit dat de compiler alle controles uitvoert tijdens het compileren, wat betekent dat const
uw programma niet vertraagt en dat u geen extra testcases hoeft te schrijven om dingen tijdens runtime te controleren.
Wat betekenen” X const& x” en “X const* p”?
X const& x
is gelijk aan const X& x
, en X const* x
is gelijk aanconst X* x
.
sommige mensen geven de voorkeur aan de const
-op-de-juiste stijl, Ze noemen het “consistent const
” of, gebruikmakend van een term bedacht door Simon Brand, “East const
.”De” East const
” stijl kan inderdaad consistenter zijn dan het alternatief: de “East const
” stijl geeft altijd de const
aan de rechterkant van wat het bevat, terwijl de andere stijl soms de const
aan de linkerkant en soms aan de rechterkant plaatst (voor const
pointer declaraties en const
member functies).
met de” East const
” stijl, een lokale variabele die const
is wordt gedefinieerd met de const
aan de rechterkant:int const a = 42;
. Een variabele static
die const
is, wordt eveneens gedefinieerd als static double const x = 3.14;
.In principe eindigt elke const
aan de rechterkant van het ding dat het bevat, inclusief de const
die rechts moet staan: const
pointer declaraties en met een const
member functie.
de “East const
” – stijl is ook minder verwarrend bij gebruik met type aliassen: Waarom hebben foo
en bar
hier verschillende typen?
using X_ptr = X*;const X_ptr foo;const X* bar;
het gebruik van de” East const
” stijl maakt dit duidelijker:
using X_ptr = X*;X_ptr const foo;X* const foobar;X const* bar;
het is hier duidelijker dat foo
en foobar
hetzelfde type zijn en dat bar
een ander type is.
de “East const
” stijl is ook meer consistent met pointer declaraties. Contrast van de traditionele stijl:
const X** foo;const X* const* bar;const X* const* const baz;
met de “East const
” stijl
X const** foo;X const* const* bar;X const* const* const baz;
ondanks deze voordelen is de const
-op-de-juiste stijl nog niet populair, dus oudere code heeft de neiging om de traditionele stijl te hebben.
slaat “X& const x” ergens op?
Nee, Het is onzin.
om erachter te komen wat de bovenstaande verklaring betekent, Lees deze van rechts naar links: “x
is een const
referentie naar een X
“. Maar dat is redundant — referenties zijn altijd const
, in die zin dat je nooit een referentie opnieuw kunt plaatsen om het naar een ander object te laten verwijzen. Nooit. Met of zonder const
.
met andere woorden, “X& const x
” is functioneel gelijk aan “X& x
“. Omdat u niets wint door deconst
na de &
toe te voegen, moet u deze niet toevoegen: het zal mensen in verwarring brengen — de const
zal sommige mensen doen denken dat de X
const
is, alsof u “const X& x
“had gezegd.
Wat is een “const member function”?
een lidfunctie die zijn object inspecteert (in plaats van muteert).
een const
member-functie wordt aangegeven door een const
achtervoegsel net na de parameterlijst van de member-functie. Ledenfuncties met een const
achtervoegsel worden “const
ledenfuncties” of “inspecteurs” genoemd.”Member-functies zonderconst
achtervoegsel worden “non-const
member-functies” of “mutators” genoemd.”
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}
de poging om unchangeable.mutate()
aan te roepen is een fout tijdens het compileren. Er is geen runtime-ruimte of speedpenalty voor const
, en u hoeft geen testcases te schrijven om het tijdens runtime te controleren.
de trailing const
op inspect()
member functie moet worden gebruikt om te betekenen dat de methode de status van het object ‘ abstract (client-visible) niet verandert. Dat is iets anders dan zeggen dat de methode de “ruwe bits” van struct
van theobject niet zal veranderen. C++ compilers zijn niet toegestaan om de “bitwise” interpretatie te nemen, tenzij ze het liasing probleem kunnen oplossen, wat normaal niet kan worden opgelost (dat wil zeggen, een niet-const
alias kan bestaan die de status van het object kan wijzigen). Een ander (belangrijk) inzicht uit dit aliasing probleem: wijzen naar een object met een pointer-to-const
garandeert niet dat het object niet zal veranderen; het belooft alleen dat het object niet zal veranderen via die pointer.
Wat is de relatie tussen een return-by-reference en een const member function?
Als u een lid van uw this
object wilt retourneren door verwijzing vanuit een inspector-methode, moet u het retourneren met reference-to-const (const X& inspect() const
) of met waarde (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!!}
het goede nieuws is dat de compiler u vaak zal betrappen als u dit verkeerd doet. In het bijzonder, als u per ongeluk een lid van uw this
object terugstuurt met een niet-const
referentie, zoals in Person::name_evil()
hierboven, zal de compilerhet vaak detecteren en u een compile-time fout geven tijdens het compileren van de ingewanden van, in dit geval,Person::name_evil()
.
het slechte nieuws is dat de compiler je niet altijd zal vangen: er zijn gevallen waarin de compiler je gewoon nooit een foutmelding zal geven tijdens het compileren.
vertaling: je moet nadenken. Als dat je bang maakt, zoek dan een ander soort werk; “denken” is geen vierletterwoord.
onthoud de” const
filosofie ” spread in deze sectie: een const
lid functie mag niet veranderen (of een beller toestaan om te veranderen) de logische status van het this
object (AKA abstract state AKA meaningwisestate). Denk aan wat een object betekent, niet hoe het intern wordt geïmplementeerd. De leeftijd en naam van een persoon zijn logischdeel van de persoon, maar de buurman en werkgever van de persoon zijn dat niet. Een inspector methode die een deel van de logische / abstracte / betekenisvolle toestand van het this
object retourneert, mag geen niet-const
pointer (of referentie) naar dat deel retourneren,ongeacht of dat deel intern is geïmplementeerd als een direct data-lid dat fysiek is ingebed in hetthis
object of op een andere manier.
hoe zit het met “const-overloading”?
const
overbelasting helpt u const
juistheid te bereiken.
const
overbelasting is wanneer u een inspector methode en een mutator methode hebt met dezelfde naam en hetzelfde aantal en type parameters. De twee verschillende methoden verschillen alleen in die zin dat de inspecteur const
is en de mutator niet-const
.
het meest voorkomende gebruik van const
overbelasting is bij de subscript operator. U moet over het algemeen proberen een van de standaard containersjablonen te gebruiken, zoals std::vector
, maar als u uw eigen klasse moet maken met een subscriptoperator, is hier de vuistregel: subscript operators komen vaak in paren.
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 // ...};
de const
subscript operator geeft een const
-referentie terug, zodat de compiler voorkomt dat bellers per ongeluk de Fred
kunnen wijzigen. De non – const
subscript operator geeft een non-const
referentie terug, wat uw manier is om uw bellers (en de compiler) te vertellen dat uw bellers het Fred
object mogen wijzigen.
wanneer een gebruiker van uw MyFredList
klasse de subscript-operator aanroept, selecteert de compiler welke overbelasting hij moet aanroepen op basis van de Consten van zijn MyFredList
. Als de beller een MyFredList a
of MyFredList& a
heeft, dan zal a
de niet-const
subscript operator oproepen, en de beller zal eindigen met een niet – const
verwijzing naar een Fred
:
stel bijvoorbeeld dat class Fred
een inspector-methode inspect() const
en een mutator-methode mutate()
heeft:
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}
als de beller echter een const MyFredList a
of const MyFredList& a
heeft, dan zal a
de const
abonnee bellen, en de beller zal eindigen met een const
verwijzing naar een Fred
. Hierdoor kan de beller de Fred
bij a
inspecteren, maar wordt voorkomen dat de beller per ongeluk de Fred
bij a
muteert/wijzigt.
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 overbelasting voor subscript-en funcall-operators wordt hier,hier, hier, hier en hier geïllustreerd.
u kunt natuurlijk ook const
-overloading gebruiken voor andere dingen dan de subscript-operator.
Hoe kan het me helpen betere klassen te ontwerpen als ik logische toestand van fysieke toestand onderscheid?
omdat dit u aanmoedigt om uw klassen van buiten naar binnen te ontwerpen in plaats van van binnen naar buiten, wat op zijn beurt uw klassen en objecten gemakkelijker te begrijpen en te gebruiken, intuïtiever, minder foutgevoelig en sneller maakt. (Oke, dat is een lichte over-vereenvoudiging. Om alle als ‘s en’ s en maar ‘ s te begrijpen, moet je gewoon de rest van dit antwoord te lezen!)
laten we dit van binnenuit begrijpen-u zult (zou) uw klassen van buitenaf — in moeten ontwerpen, maar als u nieuw bent in dit concept, is het gemakkelijker om het van binnenuit te begrijpen.
aan de binnenkant hebben uw objecten een fysieke (of concrete of bitwise) toestand. Dit is de toestand die voor programmeurs gemakkelijk te zien en te begrijpen is; het is de toestand die er zou zijn als de klasse slechts EEN C-stijl struct
zou zijn.
aan de buitenkant hebben uw objecten gebruikers van uw klasse, en deze gebruikers zijn beperkt tot het gebruik van alleen public
ledenfuncties en friend
s. Deze externe gebruikers zien het object ook als een status, bijvoorbeeld, als theobject van klasse Rectangle
is met methoden width()
, height()
en area()
, zullen uw gebruikers zeggen dat deze drie allemaal deel uitmaken van de logische (of abstracte of betekenisvolle) status van het object. Voor een externe gebruiker heeft het Rectangle
object feitelijk een gebied, zelfs als dat gebied tijdens de vlucht wordt berekend (bijvoorbeeld als de methode area()
het product van de breedte en hoogte van het object retourneert). In feite, en dit is het belangrijke punt, uw gebruikers niet weten en niet schelen hoe youimplement een van deze methoden; uw gebruikers zien nog steeds, vanuit hun perspectief, dat uw object logischerwijs een staat van breedte, hoogte en oppervlakte heeft.
het area()
voorbeeld toont een geval waarin de logische toestand elementen kan bevatten die niet direct in de fysieke toestand worden gerealiseerd. Het tegenovergestelde is ook waar: klassen verbergen soms opzettelijk een deel van de fysieke(concrete, bitwise) status van hun objecten voor gebruikers — ze bieden opzettelijk geen public
member functies offriend
s die gebruikers in staat zouden stellen om te lezen of schrijven of zelfs te weten over deze verborgen status. Dat betekent dat er bits zijn in de fysieke toestand van het object die geen overeenkomstige elementen hebben in de logische toestand van het object.
als voorbeeld van dit laatste geval kan een collectieobject zijn laatste lookup cachen in de hoop de prestaties van zijn volgende lookup te verbeteren. Deze cache maakt zeker deel uit van de fysieke toestand van het object, maar er is een intern implementatiedetail dat waarschijnlijk niet zal worden blootgesteld aan gebruikers — het zal waarschijnlijk geen deel uitmaken van de logische toestand van het object. Vertellen wat is wat is gemakkelijk als je denkt van buitenaf-in: als de gebruikers van het collection-object nu de status van de cache zelf moeten controleren, dan is de cache transparant en maakt het geen deel uit van de logicalstate van het object.
moet de Consten van mijn publieke member-functies gebaseerd zijn op wat de methode doet met de logische of fysieke toestand van het object?
logisch.
er is geen manier om dit volgende deel gemakkelijk te maken. Het gaat pijn doen. Beste aanbeveling is om te gaan zitten. En alsjeblieft, voor uw veiligheid, zorg ervoor dat er geen scherpe werktuigen in de buurt zijn.
laten we teruggaan naar het collection-object voorbeeld. Herinneren: er is een lookup methode die caches de laatste lookup in de hoop te versnellen toekomstige lookups.
laten we zeggen wat waarschijnlijk voor de hand ligt: neem aan dat de lookup methode geen wijzigingen aanbrengt in de logische status van het Collection-object.
dus … het is tijd om je pijn te doen. Ben je klaar?
hier komt: als de lookup methode geen verandering brengt in de logische status van het collection-object, maar de fysieke status van het collection-object verandert (het maakt een zeer reële verandering in de zeer reële cache), moet de lookup methode dan const
zijn?
het antwoord is een volmondig ja. (Er zijn uitzonderingen op elke regel, dus ” ja ” moet echt een sterretje ernaast,maar de overgrote meerderheid van de tijd, het antwoord is ja.)
dit gaat allemaal over “logisch const
“over” fysiek const
.”Het betekent dat de beslissing over het al dan niet versieren van amethod met const
in de eerste plaats moet afhangen van de vraag of die methode de logische staat ongewijzigd laat, ongeacht (zit je neer?) (je zou kunnen gaan zitten) ongeacht of de methode toevallig zeer reële veranderingen maakt aan de zeer reële fysieke toestand van het object.
in het geval dat niet ingezakt, of in het geval u nog geen pijn, laten we plagen het uit elkaar in twee gevallen:
- als een methode een deel van de logische toestand van het object verandert, is het logischerwijs een mutator; het zou niet
const
evenif moeten zijn (zoals in feite gebeurt!) de methode verandert geen fysieke bits van de concrete toestand van het object. - omgekeerd is een methode logischerwijs een inspecteur en zou
const
moeten zijn als het nooit enig deel van de logische toestand van het object verandert, zelfs als (zoals in werkelijkheid gebeurt! de methode verandert de fysieke bits van de concrete toestand van het object.
als u in de war bent, lees het nogmaals.
als je niet in de war bent, maar boos bent, goed: je vindt het misschien nog niet leuk, maar je begrijpt het tenminste. Haal diep adem en herhaal na mij: “de const
waarde van een methode moet zinvol zijn van buiten het object.”
als je nog steeds boos bent, herhaal dit dan drie keer: “de Consten van een methode moet zinvol zijn voor de gebruikers van het object, en die gebruikers kunnen alleen de logische status van het object zien.”
als je nog steeds boos bent, sorry, het is wat het is. Accepteer het en leef ermee. Ja, Er zullen uitzonderingen zijn; elke regel heeft ze. Maar in de regel is dit logische const
begrip goed voor u en goed voor uw software.
nog iets. Dit gaat zinloos worden, maar laten we precies zijn of een methode de logicalstate van het object verandert. Als je buiten de klasse bent-je bent een normale gebruiker, elk experiment dat je zou kunnen uitvoeren (elke methode ofvolgorde van methoden die je aanroept) zou dezelfde resultaten hebben (dezelfde return waarden, dezelfde uitzonderingen of het ontbreken van uitzonderingen)ongeacht of je voor het eerst die lookup methode hebt aangeroepen. Als de lookup functie veranderd elk toekomstig gedrag van elke toekomstige methode (niet alleen waardoor het sneller, maar veranderde de uitkomst, veranderde de return waarde, veranderde theexception), dan is de lookup methode veranderd logische toestand van het object — het is een mutuator. Maar als de lookup methode niets anders veranderd dan misschien het maken van sommige dingen sneller, dan is het een Inspecteur.
Wat moet ik doen als Ik wil dat een const-member-functie een “onzichtbare” wijziging maakt in een datalid?
gebruik mutable
(of gebruik in laatste instantie const_cast
).
een klein percentage inspecteurs moet wijzigingen aanbrengen in de fysieke toestand van een object die niet door externalusers kunnen worden waargenomen — wijzigingen in de fysieke maar niet logische toestand.
bijvoorbeeld, het eerder besproken collectieobject boekte zijn laatste lookup in de cache in de hoop de prestaties van zijn volgende lookup te verbeteren. Aangezien de cache, in dit voorbeeld, niet direct kan worden waargenomen door een deel van de openbare interface van het collection-object (anders dan timing), zijn het bestaan en de toestand ervan geen deel van de logische toestand van het object, dus wijzigingen zijn onzichtbaar voor externe gebruikers. De opzoekmethode is een inspector omdat deze nooit de logische toestand van het object verandert, ongeacht het feit dat het, althans voor de huidige implementatie, de fysieke toestand van het object verandert.
wanneer methoden de fysische maar niet logische toestand wijzigen, moet de methode in het algemeen worden gemarkeerd als const
omdat het echt een inspectormethode is. Dat levert een probleem op: wanneer de compiler uw const
methode ziet die de fysieke status van het this
object verandert, zal het klagen — het zal uw code een foutmelding geven.
de C++ compiler taal gebruikt het mutable
sleutelwoord om u te helpen deze logische const
notie te omarmen. In dit geval zou je de cache markeren met het mutable
sleutelwoord,op die manier weet de compiler dat het toegestaan is om te veranderen binnen eenconst
methode of via een andere const
pointer of referentie. In onze lingo markeert het mutable
sleutelwoord de porties van de fysieke toestand van het object die geen deel uitmaken van de logische toestand.
het mutable
sleutelwoord gaat net voor de declaratie van het data lid, dat wil zeggen, dezelfde plaats waar uconst
zou kunnen plaatsen. De andere aanpak niet de voorkeur, is om weg te werpen de const
‘heid van de this
pointer, waarschijnlijk via deconst_cast
zoekwoorden:
Set* self = const_cast<Set*>(this); // See the NOTE below before doing this!
Na deze regel, self
dezelfde bits als this
, dat is, self == this
, maar self
is een Set*
in plaats van eenconst Set*
(technisch this
is een const Set* const
, maar de meest rechtse const
is niet relevant voor deze discussie).Dat betekent dat u self
kunt gebruiken om het object waarnaar wordt verwezen met this
te wijzigen.
Opmerking: Er is een zeer onwaarschijnlijke fout die kan optreden met const_cast
. Het gebeurt alleen wanneer drie zeer zeldzaamheden tegelijkertijd worden gecombineerd: een data-lid dat mutable
zou moeten zijn( zoals hierboven is besproken), een compilerdie het mutable
sleutelwoord niet ondersteunt en/of een programmeur die het niet gebruikt, en een object dat oorspronkelijk gedefinieerd was als const
(in tegenstelling tot een normaal, niet-const
object dat wordt aangeduid met een pointer-to-const
).Hoewel deze combinatie zo zeldzaam is dat het je misschien nooit zal overkomen, werkt de code mogelijk niet (de standaard zegt dat het gedrag niet gedefinieerd is).
als u ooit const_cast
wilt gebruiken, gebruik dan mutable
. Met andere woorden, als u ooit een lid van een object moet veranderen en naar dat object wordt verwezen met een pointer-to-const
, is het veiligste en eenvoudigste om mutable
toe te voegen aan de ledenverklaring. U kunt const_cast
gebruiken als u er zeker van bent dat het object niet const
is (bijvoorbeeld als u er zeker van bent dat het object ongeveer als volgt wordt gedeclareerd: Set
s;
), maar als het object zelf const
kan zijn (bijvoorbeeld als het kan worden gedeclareerd als: const Set s;
), gebruik dan mutable
in plaats van const_cast
.
schrijf niet als je zegt dat Versie X van compiler Y op machine Z je een niet-mutable
lid van eenconst
object laat veranderen. Het kan me niet schelen — Het is illegaal volgens de taal en uw code zal waarschijnlijk mislukken op een andere compiler of zelfs een andere versie (een upgrade) van dezelfde compiler. Zeg gewoon nee. Gebruik in plaats daarvan mutable
. Schrijf codedat gegarandeerd werkt, geen code die niet lijkt te breken.
betekent const_cast gemiste optimalisatiemogelijkheden?
in theorie ja; in de praktijk Nee.
zelfs als de taal verboden is const_cast
, zou de enige manier om te voorkomen dat de register-cache over een const
memberfunction-aanroep wordt gespoeld, zijn om het aliasprobleem op te lossen (d.w.z., om te bewijzen dat er geen niet – const
pointers zijn die naar het object verwijzen). Dit kan alleen in zeldzame gevallen gebeuren (wanneer het object geconstrueerd is in het kader van de const
ledenfunctie aanroep, en wanneer alle niet – const
ledenfunctie aanroepingen tussen de constructie van het object en de const
ledenfunctie aanroep statisch gebonden zijn, en wanneer elk van deze aanroepingen ook inline
d is, en wanneer de constructor zelf inline
d is, en wanneer alle ledenfuncties de aanroep van de constructor inline
zijn).
Waarom staat de compiler me toe om een int te wijzigen nadat ik er met een const int*naar heb gewezen?
omdat ” const int* p
“betekent” p
belooft de *p
niet te veranderen, “not” *p
belooft niet te veranderen.”
waardoor een const int*
naar een int
wijst, betekent niet const
-ify de int
. De int
kan niet worden gewijzigd via deconst int*
, maar als iemand anders een int*
(opmerking: no const
) heeft die verwijst naar (“aliassen”) dezelfde int
, dan kan dieint*
worden gebruikt om de int
te wijzigen. Bijvoorbeeld:
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!) // ...}
merk op dat main()
en f(const int*,int*)
in verschillende compilatie-eenheden kunnen zijn die op verschillende dagen van de week worden samengesteld. In dat geval is er geen enkele manier waarop de compiler de aliasing kan detecteren tijdens het compileren. Daarom kunnen we geen taalregel maken die dit soort dingen verbiedt. In feite zouden we niet eens zo ‘ n arule willen maken, omdat het in het algemeen wordt beschouwd als een functie die je veel aanwijzers kunt hebben die naar hetzelfde ding wijzen. Het feit dat een van die pointers belooft het onderliggende “ding” niet te veranderen is gewoon een belofte gemaakt door de pointer; het is geen belofte gemaakt door het “ding”.
betekent” const Fred* p ” dat * p niet kan veranderen?
Nee! (Dit is gerelateerd aan de FAQ over aliasing van int
pointers.)
“const Fred* p
” betekent dat de Fred
niet kan worden gewijzigd via pointer p
, maar er kunnen andere manieren zijn om bij het object te komen zonder door eenconst
te gaan (zoals een aliased niet-const
pointer zoals een Fred*
). Als u bijvoorbeeld twee pointers “const Fred* p
” en “Fred* q
” hebt die naar hetzelfde Fred
object (aliasing) wijzen, kan pointer q
worden gebruikt om het Fred
object te wijzigen, maar pointer p
niet.
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 // ...}
Waarom krijg ik een fout bij het converteren van een Foo* * → const Foo**?
omdat het omzetten van Foo**
→ const Foo**
ongeldig en gevaarlijk zou zijn.
C++ staat de (veilige) conversie Foo*
→ Foo const*
toe, maar geeft een fout als u impliciet Foo**
→const Foo**
probeert te converteren.
de reden waarom die fout een goede zaak is wordt hieronder gegeven. Maar eerst is hier de meest voorkomende oplossing: simplychange const Foo**
naar 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* // ...}
de reden dat de conversie vanaf Foo**
→ const Foo**
gevaarlijk is, is dat het u in stilte en per ongeluk een const Foo
object laat wijzigen zonder een 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!! // ...}
als de lijn q = &p
legaal zou zijn, zou q
wijzen op p
. De volgende regel, *q = &x
, verandert p
zelf (aangezien *q
p
is) om te wijzen op x
. Dat zou een slechte zaak zijn, omdat we de const
qualifier zouden hebben verloren: p
is een Foo*
maarx
is een const Foo
. De p->modify()
lijn maakt gebruik van p
‘ s mogelijkheid om de referent te wijzigen,wat het echte probleem is, omdat we uiteindelijk een const Foo
hebben gewijzigd.Als u een crimineel onder een wettige vermomming verbergt, dan kan hij het vertrouwen dat aan die vermomming is gegeven, uitbuiten.Dat is slecht.
Gelukkig voorkomt C++ dat u dit doet: de regel q = &p
wordt door de compiler gemarkeerd als een compile-time fout. Herinnering: please do not pointer-cast uw weg rond die compile-time foutmelding. Zeg Gewoon Nee!
(Opmerking: Er is een begripsmatige overeenkomst tussen dit en het verbod om Derived**
om te zetten inBase**
.)