C szabvány++
- Const korrektség
- mi az a”const korrektség”?
- hogyan kapcsolódik a “const korrektség” a szokásos típusbiztonsághoz?
- meg kell próbálnom, hogy a dolgok “előbb” vagy “később”helyesek legyenek?
- mit jelent a” const X* p”?
- mi a különbség a “const X * p”,” X* const p “és a”const X* const p” között?
- mit jelent a “const X& x”?
- mit jelent az “X const& x” és az “X const* p”?A
- van értelme az “X& const x” – nek?
- mi az a “const tag funkció”?
- mi a kapcsolat a return-by-reference és a const tagfüggvény között?
- mi a helyzet a “const-túlterheléssel”?
- hogyan segíthet nekem jobb osztályok kialakításában, ha megkülönböztetem a logikai állapotot a fizikai állapottól?
- a nyilvános tagfüggvényeim állandóságának azon kell alapulnia, hogy a módszer mit tesz az objektum logikai állapotával vagy fizikai állapotával?
- mit tegyek, ha azt akarom, hogy egy const tag függvény “láthatatlan” változást hajtson végre egy adat tagon?
- a const_cast Elveszett optimalizálási lehetőségeket jelent?
- miért engedi meg a fordító, hogy megváltoztassak egy int-t, miután egy const int* – vel mutattam rá?
- a “const Fred* p” azt jelenti, hogy *p nem változhat?
- Miért kapok hibát egy Foo** const foo * * konvertálásakor?
Const korrektség
mi az a”const korrektség”?
jó dolog. Ez azt jelenti, hogy a const
kulcsszót használjuk a const
objektumok mutációjának megakadályozására.
például, ha egy f()
függvényt szeretne létrehozni, amely elfogadta a std::string
– ot, plusz meg akarja ígérni a hívóknaknem változtatja meg a hívó std::string
– ját, amely átkerül a f()
– ra, akkor megkaphatja a f()
– ot, hogy megkapja a std::string
paramétert…
-
void f1(const std::string& s);
// Pass hivatkozással-nak nek-const
-
void f2(const std::string* sptr);
// Pass by pointer-to-const
-
void f3(std::string s);
// Pass by value
a pass by reference-to-const
és pass by pointer-to – const
esetekben a hívóstd::string
funkcióinak f()
függvényen belüli megváltoztatására irányuló kísérleteket a fordító hibaként jelöli meg a függvényben fordítási idő. Ez az ellenőrzés teljes egészében fordítási időben történik: a const
-nek nincs futási ideje vagy sebességköltsége. A pass by value esetben (f3()
) a hívott függvény megkapja a hívó std::string
példányát. Ez azt jelenti, hogy a f3()
megváltoztathatja a helyi példányt, de a másolat megsemmisül, amikor a f3()
visszatér. Különösen a f3()
nem tudja megváltoztatni a hívó std::string
objektumát.
ellenkező példaként tegyük fel, hogy egy g()
függvényt akart létrehozni, amely elfogadta a std::string
értéket, de tudatni szeretné a hívókkal, hogy a g()
megváltoztathatja a hívó std::string
objektumát. Ebben az esetben a g()
megkaphatja astd::string
paramétert…
-
void g1(std::string& s);
// Pass hivatkozás-to-non-const
-
void g2(std::string* sptr);
// Pass by pointer-to-non-const
a const
hiánya ezekben a függvényekben azt mondja a fordítónak, hogy megengedett (de nem kötelező) megváltoztatni a hívó std::string
objektumát. Így átadhatják std::string
– jüket a f()
függvények bármelyikének, de csak f3()
(az, amelyik megkapja a paraméterét “érték szerint”) átadhatja std::string
– ját g1()
– nek vagy g2()
– nek. Ha a f1()
vagy f2()
valamelyik g()
függvényt kell hívnia, akkor a std::string
objektum helyi másolatát át kell adni a g()
függvénynek; a f1()
vagy a f2()
paraméter nem adható át közvetlenül egyik g()
függvénynek sem. Pl.,
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}
természetesen a fenti esetben a g1()
által végrehajtott változtatások a localCopy
objektumon történnek, amely lokális a f1()
– ra.különösen nem történik változás a const
paraméterben, amelyet a f1()
hivatkozással adtak át.
hogyan kapcsolódik a “const korrektség” a szokásos típusbiztonsághoz?
a paraméter const
-sségének deklarálása csak a típusbiztonság egy másik formája.
ha úgy találja, hogy a szokásos típusú biztonság segít a rendszerek helyes javításában (különösen nagy rendszerekben), akkor aconst
korrektség is segít.
a const
korrektség előnye, hogy megakadályozza, hogy véletlenül módosítson valamit, amire nem számított. Végül néhány extra billentyűleütéssel kell díszítenie a kódját (a const
kulcsszó), azzal a haszonnal, hogy elmondja a fordítónak és más programozóknak néhány további fontos szemantikai információt— olyan információkat, amelyeket a fordító a hibák megelőzésére használ, más programozók pedig dokumentációként használják.
fogalmilag el lehet képzelni, hogy a const std::string
például más osztály, mint a szokásos std::string
, mivel a const
változat fogalmilag hiányzik a nemconst
változatban elérhető különféle mutatív műveletek. Például fogalmilag elképzelheti, hogy a const std::string
egyszerűen nincs hozzárendelési operátor+=
vagy bármilyen más mutatív művelet.
meg kell próbálnom, hogy a dolgok “előbb” vagy “később”helyesek legyenek?
a nagyon, nagyon, nagyon elején.
vissza folt const
helyesség eredményez hógolyó hatás: minden const
hozzá “itt” igényel további négy kell adni “ott.”
Add const
Korán és gyakran.
mit jelent a” const X* p”?
ez azt jelenti, hogy p
egy X
osztályú objektumra mutat, de a p
nem használható a X
objektum megváltoztatására (természetesen p
is NULL
).
olvassa el jobbról balra: “p egy mutató egy állandó X-re.”
például, ha a X
osztálynak van const
tagfüggvénye, például inspect() const
, akkor rendben vanp->inspect()
. De ha a X
osztálynak van egy nemconst
tagfüggvénye, az mutate()
, akkor hiba, ha azt mondod p->mutate()
.
fontos, hogy ezt a hibát a fordító fordításkor elkapja-nem végeznek futásidejű teszteket. Ez azt jelenti, hogy a const
nem lassítja le a programot, és nem követeli meg, hogy extra teszteseteket írjon a dolgok futásidejű ellenőrzéséhez-a fordító fordítási időben végzi a munkát.
mi a különbség a “const X * p”,” X* const p “és a”const X* const p” között?
olvassa el a mutató deklarációit jobbról balra.
-
const X* p
azt jelenti, hogy ”p
egyX
– re mutat, azazconst
“: aX
objektum nem módosíthatóp
– on keresztül. -
X* const p
azt jelenti: “p
egyconst
mutató egyX
-re, amely nemconst
“: magát ap
mutatót nem változtathatja meg, de aX
objektumot ap
segítségével módosíthatja.A -
const X* const p
azt jelenti, hogy “ap
egyconst
mutató egyX
– re, azazconst
– re”: nem változtathatja meg magát ap
mutatót, sem aX
objektumot ap
segítségével.
és, ó igen, említettem már, hogy jobbról balra kell olvasnom a mutató deklarációit?
mit jelent a “const X& x”?
ez azt jelenti, hogy x
alias egy X
objektumot, de ezt a X
objektumot nem lehet megváltoztatni a x
segítségével.
olvassa el jobbról balra: “Ax
hivatkozás egy X
– re, ami const
.”
például, ha a X
osztálynak van const
tagfüggvénye, például inspect() const
, akkor rendben vanx.inspect()
. De ha a X
osztálynak van egy nemconst
tagfüggvénye, az mutate()
, akkor ez hiba, ha azt mondod x.mutate()
.
ez teljesen szimmetrikus a const-ra mutató mutatókkal, beleértve azt a tényt is, hogy a fordító elvégzi az összes ellenőrzést fordítási időben, ami azt jelenti, hogy a const
nem lassítja le a programot, és nem követeli meg, hogy extra teszteseteket írjon a dolgok futásidejű ellenőrzéséhez.
mit jelent az “X const& x” és az “X const* p”?A
X const& x
egyenértékű a const X& x
– vel, a X const* x
pedig aconst X* x
– vel.
vannak, akik a const
-on-the-right stílust részesítik előnyben, “konzisztens const
” – nek hívják, vagy Simon Brand által kitalált kifejezést használva “East const
.”Valójában az” East const
“stílus konzisztensebb lehet, mint az alternatíva: a” East const
” stílus mindig a const
– et helyezi jobbra attól, amit alkot, míg a másik stílus néha a const
– et helyezi balra és néha jobbra (a const
mutató deklarációkhoz és a const
tagfüggvényekhez).
az “East const
” stílusban a const
helyi változót a jobb oldali const
határozza meg:int const a = 42;
. Hasonlóképpen a static
változót, amely const
, static double const x = 3.14;
– ként definiáljuk.Alapvetően minden const
az általa létrehozott dolog jobb oldalán végződik, beleértve a const
– et is, amelynek a jobb oldalon kell lennie: const
mutató deklarációk és const
tagfüggvény.
az “East const
” stílus szintén kevésbé zavaró, ha típusalnevekkel használják: miért van itt a foo
és a bar
különböző típusai?
using X_ptr = X*;const X_ptr foo;const X* bar;
az “East const
” stílus használata világosabbá teszi ezt:
using X_ptr = X*;X_ptr const foo;X* const foobar;X const* bar;
itt egyértelműbb, hogy a foo
és a foobar
azonos típusúak, a bar
pedig más típusúak.
az “East const
” stílus is jobban megfelel a mutató deklarációknak. Kontraszt a hagyományos stílus:
const X** foo;const X* const* bar;const X* const* const baz;
az “East const
” stílus
X const** foo;X const* const* bar;X const* const* const baz;
ezen előnyök ellenére a const
-on-the-right stílus még nem népszerű, így a régi kód általában a hagyományos stílus.
van értelme az “X& const x” – nek?
nem, ez ostobaság.
hogy megtudja, mit jelent a fenti nyilatkozat, olvassa el jobbról balra: “Ax
egy const
hivatkozás a X
– re”. De ez redundáns-a hivatkozások mindig const
, abban az értelemben, hogy soha nem állíthatja újra a hivatkozást, hogy egy másik objektumra utaljon. Soha. A const
értékkel vagy anélkül.
más szavakkal, a “X& const x
” funkcionálisan egyenértékű a “X& x
” – vel. Mivel aconst
hozzáadása a &
után semmit sem nyer, nem szabad hozzáadnia: összezavarja az embereket — a const
arra készteti az embereket, hogy a X
const
, mintha azt mondta volna, hogy “const X& x
“.
mi az a “const tag funkció”?
olyan tagfüggvény, amely megvizsgálja (nem pedig mutálja) az objektumát.
a const
tagfüggvényt const
utótag jelzi közvetlenül a tagfüggvény paraméterlistája után. A const
utótaggal rendelkező tagfunkciókat “const
tagfunkcióknak” vagy “ellenőröknek” nevezzük.”Aconst
utótag nélküli tagfüggvényeket “nemconst
tagfüggvényeknek” vagy “mutátoroknak” nevezzük.”
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}
a unchangeable.mutate()
meghívására tett kísérlet hiba a fordításkor. Nincs futásidejű hely vagy speedpenalty a const
számára, és nem kell teszteseteket írni a futásidejű ellenőrzéshez.
a záró const
on inspect()
tag függvényt kell használni arra, hogy a módszer ne változtassa meg az objektum abstract (kliens-látható) állapotát. Ez kissé eltér attól, hogy a módszer nem változtatja meg az objektum struct
” nyers bitjeit”. A C++ fordítóprogramok csak akkor vehetik igénybe a “bitenkénti” értelmezést, ha meg tudják oldani az aliasing problémát, amelyet általában nem lehet megoldani (azaz létezhet egy nemconst
alias, amely módosíthatja az objektum állapotát). Egy másik (fontos) betekintés ebből az aliasing kérdésből: egy objektumra mutató mutató-to-const
nem garantálja, hogy az objektum nem változik; csupán azt ígéri, hogy az objektum nem változik ezen a mutatón keresztül.
mi a kapcsolat a return-by-reference és a const tagfüggvény között?
ha a this
objektum egy tagját egy inspector metódus hivatkozásával szeretné visszaadni, akkor azt a reference-to-const (const X& inspect() const
) vagy value (X inspect() const
) használatával kell visszaadnia.
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!!}
a jó hír az, hogy a fordító gyakran elkapni, ha ezt rosszul. Különösen, ha véletlenül visszaadja a this
objektum egy tagját nemconst
hivatkozással, mint például a fenti Person::name_evil()
-ben, a fordító gyakran észleli azt, és fordítási idejű hibát ad, amikor lefordítja a belsőségeket, ebben az esetbenPerson::name_evil()
.
a rossz hír az, hogy a fordító nem mindig elkapni: vannak olyan esetek, amikor a fordító egyszerűen nem evergive egy fordítási idő hibaüzenet.
Fordítás: meg kell gondolni. Ha ez megijeszt, keressen egy másik munkát; a “gondolkodás” nem négybetűs szó.
ne feledje, hogy a” const
filozófia ” elterjedt ebben a szakaszban: a const
tagfüggvénynek nem szabad megváltoztatnia (vagy lehetővé tennie a hívó fél számára, hogy megváltoztassa) a this
objektum logikai állapotát (más néven absztrakt állapot, más néven jelentéswisestate). Gondolj arra, hogy mit jelent egy objektum, nem pedig arra, hogy hogyan valósítják meg belsőleg. Egy személy életkora és neve logikailagrésze a személynek, de a személy szomszédja és munkáltatója nem. Az inspector metódus, amely a this
objektum logikai / absztrakt / jelentéstani állapotának egy részét adja vissza, nem adhat vissza nemconst
mutatót (vagy hivatkozást) arra a részre,függetlenül attól, hogy az adott rész belsőleg közvetlen adattagként van-e megvalósítva fizikailag beágyazva athis
objektumba, vagy más módon.
mi a helyzet a “const-túlterheléssel”?
const
a túlterhelés segít a const
helyesség elérésében.
const
a túlterhelés akkor jelentkezik, ha egy inspector metódus és egy mutátor metódus azonos névvel és azonos számú és típusú paraméterrel rendelkezik. A két különböző módszer csak abban különbözik egymástól, hogy a felügyelő const
, a mutátor pedig nemconst
.
a const
túlterhelés leggyakoribb használata az index operátor. Általában meg kell próbálnia használni az egyiketstandard konténer sablonok, például std::vector
, de ha saját osztályt kell létrehoznia, amelynek van egy subscriptoperator, itt van a hüvelykujjszabály: az index operátorok gyakran párban jönnek.
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 // ...};
a const
index operátor visszaad egy const
-hivatkozást, így a fordító megakadályozza a hívókat a Fred
véletlen mutálásában/megváltoztatásában. A nem-const
index operátor visszaad egy nem – const
hivatkozást, amely gyakran azt mondja a hívóknak (és a fordítónak), hogy a hívók módosíthatják a Fred
objektumot.
amikor a MyFredList
osztályod felhasználója felhívja az index operátort, a fordító kiválasztja, hogy melyik túlterhelést hívja a MyFredList
állandósága alapján. Ha a hívónak MyFredList a
vagy MyFredList& a
van, akkor a a
felhívja a nemconst
index operátort, a hívó pedig nemconst
hivatkozást kap a Fred
:
tegyük fel például, hogy class Fred
rendelkezik egy inspector-metódussal inspect() const
és egy mutor-metódussal 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}
Ha azonban a hívónak const MyFredList a
vagy const MyFredList& a
van, akkor a a
felhívja a const
subcriptoperator-t, és a hívó végül const
hivatkozást kap a Fred
– re. Ez lehetővé teszi a hívó számára, hogy megvizsgálja a Fred
értéket a
– nél, de megakadályozza, hogy a hívó akaratlanul mutálja/megváltoztassa a Fred
értéket a
– nél.
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}
az Index – és funcall-operátorok Const túlterhelését itt,itt, itt, itt és itt szemléltetjük.
természetesen használhatja a const
-túlterhelést az index operátoron kívüli dolgokra is.
hogyan segíthet nekem jobb osztályok kialakításában, ha megkülönböztetem a logikai állapotot a fizikai állapottól?
mert ez arra ösztönöz, hogy az osztályokat kívülről tervezze meg-nem pedig belülről-kifelé, ami viszont megkönnyíti az osztályok és tárgyak megértését és használatát, intuitívabbá, kevésbé hibára hajlamos és gyorsabb. (Oké, ez egy kicsit túl egyszerűsítés. Ahhoz, hogy megértsük az összes if-et, csak el kell olvasnod a többi részetválasz!)
értsük meg ezt belülről-kívülről — meg kell terveznie az osztályokat a kívülről-be, de ha új vagy ebben a koncepcióban, akkor könnyebb megérteni a kívülről-kifelé.
belül a tárgyak fizikai (vagy konkrét vagy bitenkénti) állapotban vannak. Ez az az állapot, amelyet a programozók könnyen láthatnak és megérthetnek; ez az állapot lenne ott, ha az osztály csak egy C-stílusú struct
lenne.
kívülről az objektumaidnak az osztályod felhasználói vannak, és ezek a felhasználók csak public
tagfunkciókat és friend
s-t használhatnak. Ezek a külső felhasználók az objektumot állapotként is érzékelik, például ha az objektum osztály Rectangle
módszerekkel width()
, height()
és area()
, a felhasználók azt mondanák, hogy ez a három az objektum logikai (vagy absztrakt vagy jelentéstani) állapotának része. Egy külső felhasználó számára a Rectangle
objektumnak van területe, még akkor is, ha azt menet közben számítják ki (pl. ha a area()
metódus az objektum szélességének és magasságának szorzatát adja vissza). Valójában, és ez a fontos pont, a felhasználók nem tudják, és nem érdekli, hogyan hajtsák végre ezeket a módszereket; a felhasználók még mindig érzékelik, az ő szemszögükből, hogy az objektum logikailag ugyanolyan széles, magas és terület állapotú.
a area()
példa olyan esetet mutat be, amikor a logikai állapot olyan elemeket tartalmazhat, amelyek nem valósulnak meg közvetlenül a fizikai állapotban. Az ellenkezője is igaz: az osztályok néha szándékosan elrejtik objektumaik fizikai(konkrét, bitenkénti) állapotának egy részét a felhasználók elől — szándékosan nem biztosítanak semmilyen public
tagfunkciót vagyfriend
s-t, amely lehetővé tenné a felhasználók számára, hogy olvassanak vagy írjanak, vagy akár tudjanak erről a rejtett állapotról. Ez azt jelenti, hogy az objektum fizikai állapotában vannak olyan bitek, amelyeknek nincsenek megfelelő elemei az objektum logikai állapotában.
ez utóbbi eset példájaként egy gyűjtemény-objektum gyorsítótárazhatja utolsó keresését abban a reményben, hogy javítja a következő keresés teljesítményét. Ez a gyorsítótár minden bizonnyal része az objektum fizikai állapotának, de ott van egy belső végrehajtási részlet, amely valószínűleg nem lesz kitéve a felhasználóknak — valószínűleg nem lesz része az objektum logikai állapotának. Mondani, mi az, ami könnyű, ha úgy gondolja, kívülről-a: ha a gyűjtemény-objektum felhasználóinak most van módjuk ellenőrizni a gyorsítótár állapotát, akkor a gyorsítótár átlátszó, és nem része az objektum logikai állapotának.
a nyilvános tagfüggvényeim állandóságának azon kell alapulnia, hogy a módszer mit tesz az objektum logikai állapotával vagy fizikai állapotával?
logikus.
nincs mód arra, hogy ezt a következő részt megkönnyítsük. Fájni fog. A legjobb ajánlás az, hogy üljön le. És kérlek, a biztonságod érdekében, győződj meg róla, hogy nincsenek éles eszközök a közelben.
térjünk vissza a collection-object példához. Ne feledje: van egy keresési módszer, amelyeléri az utolsó keresést a jövőbeni keresések felgyorsítása érdekében.
tegyük fel, ami valószínűleg nyilvánvaló: tegyük fel, hogy a keresési módszer nem módosítja a gyűjtemény-objektum logikai állapotát.
tehát… eljött az idő, hogy bántsalak. Készen van?
itt jön: ha a keresési módszer nem változtatja meg a gyűjtemény-objektum logikai állapotát, de megváltoztatja a gyűjtemény-objektum fizikai állapotát (ez nagyon valós változást eredményez a nagyon valós gyorsítótárban), akkor a keresési metódusnak const
– nek kell lennie?
a válasz hangos igen. (Minden szabály alól vannak kivételek,tehát az “Igen” mellett valóban csillagnak kell lennie, de az esetek túlnyomó többségében a válasz igen.)
ez az egész a “logikai const
” felett “fizikai const
.”Ez azt jelenti, hogy a döntést arról, hogy díszíteni amethod const
kell függenie elsősorban attól, hogy ez a módszer hagyja a logikai állapot változatlan, függetlenül attól, hogy (ülsz?) (érdemes leülni) függetlenül attól, hogy a módszer nagyon valós változásokat hoz-e az objektum nagyon valós fizikai állapotában.
abban az esetben, ha ez nem süllyedt be, vagy ha még nem fáj, akkor két esetre osztjuk szét:
- ha egy metódus megváltoztatja az objektum logikai állapotának bármely részét, akkor logikusan mutátor; nem lehet
const
evenif (ahogy valójában történik!) a módszer nem változtatja meg az objektum konkrét állapotának fizikai bitjeit. - ezzel szemben egy metódus logikailag inspector, és
const
– nek kell lennie, ha soha nem változtatja meg az objektum logikai állapotát, még akkor sem, ha (ahogy valójában történik!) a módszer megváltoztatja az objektum konkrét állapotának fizikai bitjeit.
ha összezavarodtál, olvasd el újra.
ha nem vagy összezavarodva, de dühös vagy, jó: lehet, hogy még nem tetszik, de legalább megérted. Vegyünk egy mély lélegzet, és ismételje meg utánam: “a const
ness módszer kell értelme kívülről a tárgy.”
ha még mindig dühös vagy, ismételd meg háromszor: “a metódus állandóságának értelmesnek kell lennie az objektum felhasználói számára, és ezek a felhasználók csak az objektum logikai állapotát láthatják.”
ha még mindig dühös vagy, sajnálom, ez az, ami. Szívd fel és élj vele együtt. Igen, lesznek kivételek; minden szabálynak megvan. De általában ez a logikai const
fogalom jó neked és jó a szoftverednek.
még egy dolog. Ez őrült lesz, de legyünk pontosak abban, hogy egy módszer megváltoztatja-e az objektum logikai állapotát. Ha kívül esik az osztályon — normál felhasználó vagy, akkor minden kísérlet, amelyet elvégezhet (minden hívott módszer vagy módszersorozat), ugyanazokkal az eredményekkel jár (ugyanazok a visszatérési értékek, ugyanazok a kivételek vagy kivételek hiánya), függetlenül attól, hogy először hívta-e meg ezt a keresési módszert. Ha a keresési függvény megváltoztatta bármely jövőbeli viselkedését (nem csak gyorsabbá tette, hanem megváltoztatta az eredményt, megváltoztatta a visszatérési értéket, megváltoztatta az exception — t), akkor a keresési módszer megváltoztatta az objektum logikai állapotát-ez egy kölcsönző. De ha a keresési módszer nem változott mást, mint talán néhány dolgot gyorsabbá tenni, akkor ez egy ellenőr.
mit tegyek, ha azt akarom, hogy egy const tag függvény “láthatatlan” változást hajtson végre egy adat tagon?
használja a mutable
(vagy végső megoldásként használja a const_cast
lehetőséget).
az ellenőrök kis százalékának módosítania kell egy objektum fizikai állapotát, amelyet a külső felhasználók nem tudnak megfigyelni — a fizikai, de nem logikai állapot változásai.
például a korábban tárgyalt gyűjteményobjektum gyorsítótárazta az utolsó keresést, remélve, hogy javítja a következő keresés teljesítményét. Mivel a gyorsítótárat ebben a példában a gyűjtemény-objektum nyilvános felületének egyetlen része sem tudja közvetlenül megfigyelni (az időzítésen kívül), létezése és állapota nem része az objektum logikai állapotának, így a változások láthatatlanok a külső felhasználók számára. A keresési módszer ellenőr, mivel soha nem változtatja meg az objektum logikai állapotát, függetlenül attól, hogy legalább a jelenlegi megvalósítás szempontjából megváltoztatja az objektum fizikai állapotát.
amikor a módszerek megváltoztatják a fizikai, de nem logikai állapotot, a módszert általában const
-ként kell megjelölni, mivel valójában egy inspector-módszer. Ez problémát okoz: amikor a fordító meglátja a const
metódust, amely megváltoztatja a this
objektum fizikai állapotát, panaszkodni fog — hibaüzenetet ad a kódjának.
a C++ fordító nyelve a mutable
kulcsszót használja, hogy segítsen megragadni ezt a logikai const
fogalmat. Ebben az esetben a gyorsítótárat a mutable
kulcsszóval jelöljük meg, így a fordító tudja, hogy megengedett aconst
metóduson belül vagy bármely más const
mutatón vagy hivatkozáson keresztül. Nyelvünkben a mutable
kulcsszó jelöli az objektum fizikai állapotának azokat a részeit, amelyek nem részei a logikai állapotnak.
a mutable
kulcsszó közvetlenül az adattag nyilatkozata előtt megy, vagyis ugyanazon a helyen, ahol aconst
– et elhelyezheti. A másik, nem preferált megközelítés a const
‘a this
mutató értéke, valószínűleg aconst_cast
kulcsszó:
Set* self = const_cast<Set*>(this); // See the NOTE below before doing this!
e sor után a self
ugyanazokkal a bitekkel fog rendelkezni, mint a this
, Vagyis self == this
, de a self
inkább Set*
, mintconst Set*
(technikailag a this
const Set* const
, de a jobb oldali const
lényegtelen ebben a vitában).Ez azt jelenti, hogy a self
használatával módosíthatja a this
által mutatott objektumot.
megjegyzés: rendkívül valószínűtlen hiba fordulhat elő const_cast
esetén. Csak akkor fordul elő, ha három nagyon ritka dolgot kombinálnak egyszerre: olyan adattag, amelynek mutable
-nek kell lennie (mint a fentiekben tárgyaltuk), egy fordító, amely nem támogatja a mutable
kulcsszót és/vagy egy programozó, aki nem használja, és egy objektum, amelyet eredetileg const
-nek definiáltak (szemben egy normál, nemconst
objektummal, amelyre egy mutató mutat-to – const
).Bár ez a kombináció olyan ritka, hogy soha nem történhet meg veled, ha valaha is megtörtént, a kód nem működik (a szabvány szerint a viselkedés nincs meghatározva).
ha valaha is használni szeretné a const_cast
– ot, akkor használja a mutable
– et. Más szóval, ha valaha is meg kell változtatnod az anobject egy tagját, és az objektumra mutat egy mutató-to-const
, a legbiztonságosabb és legegyszerűbb dolog, ha hozzáadod a mutable
értéket a tag nyilatkozatához. Használhatja a const_cast
– ot, ha biztos benne, hogy a tényleges objektum nem const
(pl. ha biztos benne, hogy az objektum valami ilyesmit deklarál: Set
s;
), de ha maga az objektum lehet const
(pl. ifit deklarálható: const Set s;
), akkor használja a mutable
– et a const_cast
helyett.
kérjük, ne írjon mondván version X fordító Y gép Z lehetővé teszi, hogy módosítsa a nemmutable
tagja egyconst
objektumot. Nem érdekel — a nyelv szerint illegális, és a kód valószínűleg sikertelen lesz egy másik fordítóprogramon, vagy akár ugyanazon fordító más verzióján (frissítésén). Csak mondj nemet. Használja helyette a mutable
értéket. Írj kódamely garantáltan működik, nem olyan kód, amely úgy tűnik, hogy nem törik meg.
a const_cast Elveszett optimalizálási lehetőségeket jelent?
elméletben igen; a gyakorlatban nem.
még akkor is, ha a nyelv törvényen kívül helyezte const_cast
, az egyetlen módja annak, hogy elkerülje a regiszter gyorsítótárának átöblítését egy const
tagfunkció hívás esetén, az aliasing probléma megoldása (azaz., annak bizonyítására, hogy nincsenek nemconst
mutatók, amelyek az objektumra mutatnak). Ez csak ritka esetekben fordulhat elő (amikor az objektum a const
tagfüggvény-meghívás hatókörében van felépítve, és amikor az objektum felépítése és aconst
tagfüggvény-meghívás közötti összes nemconst
tagfüggvény-meghívás statikusan kötött, és amikor ezen meghívások mindegyike szintén inline
d, és amikor maga a konstruktor inline
d, és amikor bármely tagfüggvény a konstruktor hívásai inline
).
miért engedi meg a fordító, hogy megváltoztassak egy int-t, miután egy const int* – vel mutattam rá?
mivel a “const int* p
” azt jelenti, hogy “p
megígéri, hogy nem változtatja meg a *p
,” nem “*p
megígéri, hogy nem változik.”
ha a const int*
egy int
-re mutat, az nem const
– ify a int
. A int
nem változtatható meg aconst int*
– en keresztül, de ha valaki másnak van egy int*
(megjegyzés: nincs const
), amely ugyanarra a int
– re mutat (“álnevek”), akkor ez aint*
használható a int
megváltoztatására. Például:
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!) // ...}
vegye figyelembe, hogy a main()
és f(const int*,int*)
különböző összeállítási egységekben lehetnek, amelyek a hét különböző napjain vannak összeállítva. Ebben az esetben a fordító nem tudja észlelni az álnevet fordításkor. Ezért nincs mód arra, hogy olyan nyelvi szabályt alkossunk, amely tiltja az ilyesmit. Valójában nem is szeretnénk ilyen szabályt készíteni, mivel általában olyan tulajdonságnak tekintik, hogy sok mutató mutathat ugyanarra a dologra. Az a tény, hogy az egyik mutató azt ígéri, hogy nem változtatja meg az alapul szolgáló “dolgot”, csak a mutató ígérete; ez nem a “dolog”ígérete.
a “const Fred* p” azt jelenti, hogy *p nem változhat?
nem! (Ez a int
mutatók álnévvel kapcsolatos GYIK-hez kapcsolódik.)
“const Fred* p
” azt jelenti, hogy a Fred
nem változtatható meg a mutatóval p
, de lehet, hogy más módon is eljuthat az objektumhoz anélkül, hogy átmenne a const
(például egy álneves nemconst
mutató, például a Fred*
). Például, ha két “const Fred* p
” és “Fred* q
” mutató van, amelyek ugyanarra a Fred
objektumra mutatnak (álnév), a q
mutató használható a Fred
objektum megváltoztatására, de a p
mutató nem.
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 // ...}
Miért kapok hibát egy Foo** const foo * * konvertálásakor?
mert a Foo**
const Foo**
konvertálása érvénytelen és veszélyes lenne.
a C++ lehetővé teszi a (biztonságos) konverziót Foo*
Foo const*
, de hibát ad, ha megpróbálja implicit módon konvertálni Foo**
const Foo**
.
az alábbiakban bemutatjuk, miért jó ez a hiba. De először itt van a leggyakoribb megoldás: egyszerűenváltoztassa meg a const Foo**
– et const Foo* const*
– ra:
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* // ...}
a Foo**
const Foo**
– ről való átalakítás azért veszélyes, mert lehetővé teszi, hogy csendben és véletlenül módosíts egy const Foo
tárgyat öntvény nélkül:
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!! // ...}
ha a q = &p
vonal legális lenne, a q
a p
– ra mutatna. A következő sor, *q = &x
, maga a p
(mivel a *q
p
) a x
pontra mutat. Ez rossz dolog lenne, mivel elvesztettük volna a const
minősítőt: a p
egyFoo*
, de a x
egy const Foo
. A p->modify()
vonal kihasználja p
azon képességét, hogy módosítsa a referensét,ami az igazi probléma, mivel végül módosítottuk a const Foo
.
analógia útján, ha egy bűnözőt törvényes álruhában rejtenek el, akkor kihasználhatja az álruhába vetett bizalmat.Az rossz.
szerencsére a C++ megakadályozza ezt: a q = &p
sort a C++ fordító fordítási idejű hibaként jelöli meg. Emlékeztető: kérjük, ne pointer-öntött az utat körül, hogy fordítási idejű hibaüzenet. Csak Mondj Nemet!
(megjegyzés: van egy fogalmi hasonlóság E között és a Derived**
Base**
– ra való áttérés tilalma között.)