Vakio C++
- Konst-oikeellisuus
- mitä on “konst-oikeellisuus”?
- miten “const-oikeellisuus” liittyy tavalliseen tyyppiturvallisuuteen?
- Pitäisikö minun yrittää saada asiat korjattua “ennemmin” vai “myöhemmin”?
- mitä tarkoittaa “const X * p”?
- Mitä eroa on “const X * p”: llä, “X* const p”: llä ja “const X* const p: llä”?
- mitä tarkoittaa “const x& x”?
- mitä tarkoittavat “X const& x” ja “X const* p”?
- onko “X& const x” mitään järkeä?
- mikä on “const-jäsenfunktio”?
- mikä on return-by-referencen ja const-jäsentoiminnon välinen suhde?
- mikä on “konst-ylilataus”?
- miten se voi auttaa minua suunnittelemaan parempia luokkia, jos erotan loogisen tilan fyysisestä tilasta?
- pitäisikö julkisten jäsentoimintojeni vakavuuden perustua siihen, mitä menetelmä tekee olion loogiselle eli fysikaaliselle tilalle?
- mitä teen, jos haluan konst-jäsenfunktion tekevän “näkymättömän” muutoksen datajäseneen?
- tarkoittaako const_cast menetettyjä optimointimahdollisuuksia?
- miksi kääntäjä sallii minun muuttaa int: tä osoitettuani sitä konst int*: lla?
- tarkoittaako “const Fred * p” sitä, että *p ei voi muuttua?
- Miksi minulle tulee virhe muunnettaessa Foo** → const Foo**?
Konst-oikeellisuus
mitä on “konst-oikeellisuus”?
hyvä juttu. Se tarkoittaa avainsanan const
käyttämistä estämään const
objektien mutatoituminen.
esimerkiksi, jos haluat luoda funktion f()
, joka hyväksyy std::string
, sekä haluat luvata soittajille, etteivät he muuta soittajan std::string
, joka siirtyy f()
: iin, voit saada f()
: n std::string
parametrin…
-
void f1(const std::string& s);
// Pass by reference-to-const
-
void f2(const std::string* sptr);
// Ohita osoitin-kohteeseen-const
-
void f3(std::string s);
// Pass by value
in the pass by reference-to – const
and pass by pointer-to – const
cases, any trips to change the caller ‘ sstd::string
in the f()
functions would be flaged by the compiler as an error at käännösaika. Tämä tarkistus tehdään kokonaan käännösaikaan: const
: lle ei ole ajonaikaista tilaa tai nopeuskustannuksia. Pass by value-tapauksessa (f3()
) kutsuttu funktio saa kopion soittajan std::string
. Tämä tarkoittaa, että f3()
voi muuttaa sijaintipaikkaansa, mutta kopio tuhoutuu, kun f3()
palaa. Erityisesti f3()
ei voi muuttaa soittajan std::string
kohdetta.
päinvastaisena esimerkkinä oletetaan, että haluat luoda funktion g()
, joka hyväksyy std::string
, mutta haluat ilmoittaa soittajille, että g()
saattaa muuttaa soittajan std::string
objektia. Tällöin voit saada g()
vastaanottaa senstd::string
parametrin…
-
void g1(std::string& s);
// Pass by reference-to-non-const
-
void g2(std::string* sptr);
// Ohita osoitin-non-const
const
puuttuminen näistä funktioista kertoo kääntäjälle, että niillä on lupa (mutta ei vaadita) muuttaa thecallerin std::string
– objektia. Näin ne voivat siirtää std::string
: nsä mille tahansa f()
funktiolle, mutta vain f3()
(se, joka saa parametrinsa “arvon mukaan”) voi siirtää sen std::string
: lle g1()
tai g2()
. Jos f1()
tai f2()
on kutsuttava joko g()
funktiota, g()
funktiolle on siirrettävä paikallinen kopio std::string
– objektista; f1()
tai f2()
– parametria ei voida suoraan siirtää kummallekaan g()
– funktiolle. Esim..,
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}
luonnollisesti edellä mainitussa tapauksessa kaikki muutokset, jotka g1()
tekevät, tehdään localCopy
olioon, joka on paikallinen f1()
.etenkään const
parametriin, joka ohitettiin viittauksella f1()
, ei tehdä muutoksia.
miten “const-oikeellisuus” liittyy tavalliseen tyyppiturvallisuuteen?
parametrin const
-ness julistaminen on vain yksi tyyppiturvallisuuden muoto.
jos havaitset, että tavallinen turvallisuus auttaa sinua saamaan järjestelmät oikein (se auttaa; varsinkin suurissa järjestelmissä), huomaat, ettäconst
oikeellisuus auttaa myös.
const
oikeellisuuden etuna on se, että se estää sinua vahingossa muokkaamasta jotain, mitä et odottanut muutettavan. Joudut lopulta koristelemaan koodisi muutamalla ylimääräisellä painalluksella (const
avainsana), jolloin etusi on kertoa kääntäjälle ja muille ohjelmoijille lisää tärkeää semanttista tietoa— tietoa, jota Kääntäjä käyttää estääkseen virheitä ja muut ohjelmoijat käyttävät dokumentaationa.
käsitteellisesti voi kuvitella, että esimerkiksi const std::string
on eri luokkaa kuin tavallinen std::string
, koska const
-muunnoksesta puuttuvat käsitteellisesti ne erilaiset mutatiiviset operaatiot,jotka ovat käytettävissä ei – const
– muunnoksessa. Esimerkiksi voidaan käsitteellisesti kuvitella, että const std::string
: llä ei yksinkertaisesti ole tehtäväoperaattoria+=
tai mitään muutakaan mutatiivista operaatiota.
Pitäisikö minun yrittää saada asiat korjattua “ennemmin” vai “myöhemmin”?
aivan alussa.
Selänteen paikkaaminen const
oikeellisuus johtaa lumipalloefektiin: jokainen const
lisää “over here” vaatii neljä lisää lisättäväksi “over there.”
lisää const
aikaisin ja usein.
mitä tarkoittaa “const X * p”?
se tarkoittaa p
pistettä luokan X
objektille, mutta p
ei voi muuttaa sitä X
objektia (luonnollisesti p
voisi myös NULL
).
lue se oikealta vasemmalle: “p on osoitin X: lle, joka on vakio.”
esimerkiksi jos luokassa X
on const
jäsenfunktio kuten inspect() const
, voidaan sanoap->inspect()
. Mutta jos luokassa X
on ei – const
jäsenfunktio nimeltään mutate()
, on virhe, jos sanotaan p->mutate()
.
merkitsevästi tämä virhe jää kääntäjän haaviin compilessa-aika — ajotestejä ei tehdä. Tämä tarkoittaa, että const
ei hidasta ohjelmaasi eikä vaadi ylimääräisten testitapausten kirjoittamista asioiden tarkistamiseksi ajonaikana-thecompiler tekee työn compile — time-sivustolla.
Mitä eroa on “const X * p”: llä, “X* const p”: llä ja “const X* const p: llä”?
Lue osoitinilmoitukset oikealta vasemmalle.
-
const X* p
tarkoittaa ”p
pistettäX
, joka onconst
“:X
kohdetta ei voi muuttaap
kautta. -
X* const p
tarkoittaa ”p
onconst
osoitinX
, joka ei ole –const
“: itse osoitintap
ei voi muuttaa, muttaX
kohdetta voi muuttaap
kautta. -
const X* const p
tarkoittaa ”p
onconst
osoitinX
, joka onconst
“: osoitintap
ei voi itse muuttaa, eikäX
kohdettap
.
Ja, oi kyllä, mainitsinko lukea osoittimen julistukset oikealta vasemmalle?
mitä tarkoittaa “const x& x”?
se tarkoittaa x
peitenimiä X
objektia, mutta X
objektia ei voi muuttaa x
.
lue se oikealta vasemmalle: “x
on viittaus an X
, joka on const
.”
esimerkiksi jos luokassa X
on const
jäsenfunktio kuten inspect() const
, voidaan sanoax.inspect()
. Mutta jos luokassa X
on ei – const
jäsenfunktio nimeltään mutate()
, se on virhe, jos sanot x.mutate()
.
tämä on täysin symmetrinen osoittimien kanssa const, mukaan lukien se, että kääntäjä tekee kaiken tarkistuksen compile-time, mikä tarkoittaa, että const
ei hidasta ohjelmaasi eikä vaadi sinua kirjoittamaan ylimääräisiä testitapauksia tarkistaaksesi asioita suorituksen aikana.
mitä tarkoittavat “X const& x” ja “X const* p”?
X const& x
vastaa const X& x
ja X const* x
vastaaconst X* x
.
jotkut suosivat const
– oikeaa tyyliä, kutsuen sitä “johdonmukaiseksi const
” tai Simon Brandin keksimää termiä käyttäen ” Itä const
.”Itse asiassa” Itä const
” – tyyli voi olla johdonmukaisempi kuin vaihtoehto:” Itä const
” – tyyli jättää aina const
: n oikealle siitä, mitä se antaa, kun taas toinen tyyli asettaa const
: n joskus vasemmalle ja joskus oikealle (const
: n osoitinilmoitukset ja const
: n jäsenfunktiot).
” Itä const
” – tyylillä määritellään paikallinen muuttuja, joka on const
, kun const
on oikealla:int const a = 42;
. Vastaavasti static
muuttuja, joka on const
, määritellään static double const x = 3.14;
.Periaatteessa jokainen const
päätyy sen asian oikealle puolelle, jonka se antaa, mukaan lukien const
, jonka on oltava oikealla: const
osoitinilmoitukset ja jossa on const
jäsenfunktio.
“Itä const
” – tyyli on myös vähemmän sekava, kun sitä käytetään tyyppinimien kanssa: miksi foo
ja bar
tässä on eri tyyppejä?
using X_ptr = X*;const X_ptr foo;const X* bar;
“Itä const
” – tyylin käyttäminen selkeyttää:
using X_ptr = X*;X_ptr const foo;X* const foobar;X const* bar;
tässä on selvempää, että foo
ja foobar
ovat samaa tyyppiä ja että bar
on eri tyyppiä.
“Itä const
” – tyyli on myös johdonmukaisempi osoitinilmaisujen kanssa. Kontrasti perinteinen tyyli:
const X** foo;const X* const* bar;const X* const* const baz;
“Itä const
” – tyylin
X const** foo;X const* const* bar;X const* const* const baz;
näistä eduista huolimatta const
-oikea tyyli ei ole vielä suosittu, joten legacy-koodilla on tapana olla perinteinen tyyli.
onko “X& const x” mitään järkeä?
ei, se on hölynpölyä.
saadaksesi selville, mitä yllä oleva julistus tarkoittaa, lue se oikealta vasemmalle: x
is a const
reference to a X
“. Mutta se on tarpeetonta — viittaukset ovat aina const
siinä mielessä, että viittausta ei voi koskaan resetoida niin, että se viittaisi toiseen kohteeseen. Koskaan. const
kanssa tai ilman.
toisin sanoen “X& const x
” vastaa toiminnallisesti “X& x
“. Koska et saa mitään lisäämälläconst
jälkeen &
, sinun ei pitäisi lisätä sitä: se hämmentää ihmisiä – const
saa jotkut luulemaan, että X
on const
, ikään kuin olisit sanonut ” const X& x
“.
mikä on “const-jäsenfunktio”?
jäsenfunktio, joka tarkastaa (eikä mutatoi) kohteensa.
a const
jäsenfunktio merkitään const
– loppuliitteellä heti jäsenfunktion parametrilistan jälkeen. Jäsenfunktioita, joiden pääte on const
, kutsutaan ” const
jäsenfunktioiksi “tai” tarkastajiksi.”Jäsenfunktioita, joilla ei oleconst
päätettä, kutsutaan “ei-const
jäsenfunktioiksi “tai” mutaattoreiksi.”
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}
soittoyritys unchangeable.mutate()
on käännösaikaan pyydetty virhe. const
: lle ei ole runtime-tilaa tai speedpenaliteettia, eikä sen tarkistamiseen ajonaikana tarvitse kirjoittaa testitapauksia.
perään const
on inspect()
jäsenfunktio tulee käyttää tarkoittamaan, että menetelmä ei muuta olionabstract-tilaa (client-visible). Tämä on hieman eri asia kuin sanoa, että menetelmä ei muuta theobject: n struct
“raakoja bittejä”. C++ – kääntäjät eivät saa ottaa “bitwise”-tulkintaa, elleivät he voi ratkaista aliasing-ongelmaa, jota ei normaalisti voida ratkaista (ts.ei – const
alias voisi olla olemassa, joka voisi muuttaa olion tilaa). Toinen (tärkeä) oivallus tästä aliasing kysymys: kohteen osoittaminen osoittimella-const
ei takaa, että kohde ei muutu; se vain lupaa, että objekti ei muutu osoittimen kautta.
mikä on return-by-referencen ja const-jäsentoiminnon välinen suhde?
jos haluat palauttaa this
kohteesi jäsenen tarkastajamenetelmän perusteella, sinun tulee palauttaa se käyttämällä viittausta-to-const (const X& inspect() const
) tai arvon (X inspect() const
) mukaan.
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!!}
hyvä uutinen on, että kääntäjä saa sinut usein kiinni, jos erehdyt. Erityisesti, jos vahingossa palauttaa jäsenen this
objekti ei – const
viite, kuten Person::name_evil()
edellä, laatija usein havaita sen ja antaa sinulle kääntää-aika virhe kokoamalla sisukset, tässä tapauksessa,Person::name_evil()
.
huono uutinen on se, että kääntäjä ei aina saa sinua kiinni: on tapauksia, joissa kääntäjä ei yksinkertaisesti koskaan anna sinulle käännösaikaista virheilmoitusta.
käännös: sinun täytyy ajatella. Jos se pelottaa sinua, Etsi toinen työ; “ajatella” ei ole nelikirjaiminen sana.
muista koko tähän osioon levinnyt “const
filosofia”: const
jäsenfunktio ei saa muuttaa (tai antaa soittajan muuttaa) this
olion loogista tilaa (AKA abstract state AKA meaningwisestate). Ajattele, mitä jokin esine tarkoittaa, älä sitä, miten se toteutetaan sisäisesti. Henkilön ikä ja nimi ovat logicallypart henkilön, mutta henkilön naapuri ja työnantaja eivät. Inspector-menetelmä, joka palauttaa osan this
olion loogisesta / abstraktista / merkityksellisestä tilasta, ei saa palauttaa ei – const
osoitinta (tai viittausta) kyseiseen osaan riippumatta siitä,onko kyseinen osa sisäisesti toteutettu suorana dataosana, joka on fyysisesti upotettuthis
olioon tai muulla tavalla.
mikä on “konst-ylilataus”?
const
ylikuormitus auttaa saavuttamaan const
oikeellisuuden.
const
ylikuormitus on, kun käytössä on tarkastaja-ja mutaatiomenetelmä, jolla on sama nimi ja sama määrä ja tyypit parametreja. Nämä kaksi erillistä menetelmää eroavat toisistaan vain siinä, että tarkastaja on const
ja mutaattori ei – const
.
yleisimmin const
ylikuormitusta käytetään alaindeksioperaattorilla. Sinun pitäisi yleensä yrittää käyttää jotain standardia kontti malleja, kuten std::vector
, mutta jos sinun täytyy luoda oma luokka, joka on subscriptoperator, tässä on nyrkkisääntö: alaindeksi operaattorit tulevat usein pareittain.
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 // ...};
const
alaindeksioperaattori palauttaa const
-referenssin, joten kääntäjä estää soittajia vahingossa muokkaamasta / muuttamasta Fred
: ää. Ei – const
alaindeksi-operaattori palauttaa ei – const
– viitteen, joka on sinun tapasi ilmoittaa soittajillesi (ja kääntäjälle), että soittajillasi on lupa muokata Fred
– objektia.
kun MyFredList
– luokkasi käyttäjä soittaa alaindeksioperaattorille, kääntäjä valitsee kumman ylikuormituksen soittaa perustuen niiden MyFredList
vakioon. Jos soittajalla on MyFredList a
tai MyFredList& a
, niin a
soittaa ei – const
alaindeksioperaattorille, ja soittaja päätyy ei – const
viittaukseen a Fred
:
Oletetaan esimerkiksi, että class Fred
on inspector-menetelmä inspect() const
ja mutator-menetelmä 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}
kuitenkin jos soittajalla on const MyFredList a
tai const MyFredList& a
, niin a
soittaa const
aliohjaajaan, ja soittaja päätyy const
viittaukseen Fred
. Näin soittaja voi tarkastaa Fred
: n taajuudella a
, mutta se estää soittajaa epähuomiossa muuntamasta/muuttamasta Fred
: n taajuutta 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 overloading for alaindeksi-and funcall-operators is illustrated here, here, here, here, here and here.
voit tietysti käyttää const
– ylilatausta myös muille asioille kuin alaindeksioperaattorille.
miten se voi auttaa minua suunnittelemaan parempia luokkia, jos erotan loogisen tilan fyysisestä tilasta?
koska se kannustaa sinua suunnittelemaan luokkasi ulkoa-eikä sisältä-ulos, mikä puolestaan tekee luokistasi ja esineistäsi helpommin ymmärrettäviä ja käytettäviä, intuitiivisempia, vähemmän virhealttiita ja nopeampia. (Okei, se ‘ on hieman yli-yksinkertaistaminen. Ymmärtää kaikki jos JA n ja mutta, sinun täytyy vain lukea loput thisanswer!)
ymmärretäänpä tämä sisältä-ulos — sinun tulee (pitäisi) suunnitella luokkasi ulkopuolelta, mutta jos olet uusi tässä konseptissa, se on helpompi ymmärtää sisäsivulta.
sisäpuolella esineillä on fyysinen (tai konkreettinen tai bitwise) tila. Tämä on se tila, joka ohjelmoijien on helppo nähdä ja ymmärtää; se on se tila, joka olisi olemassa, jos luokka olisi vain C-tyyli struct
.
ulkopuolella kohteillasi on oman luokkasi käyttäjiä, ja nämä käyttäjät saavat käyttää vain public
jäsentoimintoja ja friend
s. Nämä ulkopuoliset käyttäjät myös mieltävät objektin tilaksi, esimerkiksi jos theobject on luokkaa Rectangle
metodeilla width()
, height()
ja area()
, käyttäjät sanoisivat, että nämä kolme ovat kaikki osa objektin loogista (tai abstraktia tai merkityksellistä) tilaa. Ulkopuoliselle käyttäjälle Rectangle
– objektilla on pinta-ala, vaikka se laskettaisiin lennossa (esim.jos area()
– menetelmä palauttaa kohteen leveys-ja korkeustuloksen). Itse asiassa, ja tämä on tärkeä asia, käyttäjät eivät tiedä ja eivät välitä, miten voit toteuttaa mitään näistä menetelmistä; käyttäjät edelleen havaitsevat, heidän näkökulmastaan, että objekti loogisesti on ameaningwise tila leveys, korkeus, ja alue.
area()
esimerkki osoittaa tapauksen, jossa looginen tila voi sisältää elementtejä, jotka eivät suoraan toteudu physikaalisessa tilassa. Asia on myös päinvastoin: luokat piilottavat joskus tarkoituksellisesti osan olioidensa fyysisestä(konkreettisesta, bitwise—) tilasta käyttäjiltä-ne eivät tarkoituksella tarjoa mitään public
jäsentoimintoja taifriend
s-toimintoja, joiden avulla käyttäjät voisivat lukea tai kirjoittaa tai edes tietää tästä piilotilasta. Tämä tarkoittaa, että objektin fysikaalisessa tilassa on arebittejä, joilla ei ole vastaavia elementtejä objektin loogisessa tilassa.
esimerkkinä tästä jälkimmäisestä tapauksesta kokoelma-objekti saattaa kätkeä viimeisen hakunsa toivoen parantavansa seuraavan hakunsa suorituskykyä. Tämä välimuisti on varmasti osa objektin fyysistä tilaa, mutta siellä se on sisäinen toteutuksen yksityiskohta, joka ei todennäköisesti altistu käyttäjille — se ei todennäköisesti ole osa objektin epäloogista tilaa. Kertoa mikä on mikä on helppoa, jos ajattelet ulkopuolelta-sisään: jos kokoelma-objektin käyttäjät voivat nyt tarkistaa itse välimuistin tilan, välimuisti on läpinäkyvä, eikä se ole osa kohteen logiikkatilaa.
pitäisikö julkisten jäsentoimintojeni vakavuuden perustua siihen, mitä menetelmä tekee olion loogiselle eli fysikaaliselle tilalle?
looginen.
seuraavaa osaa ei voi tehdä helpoksi. Se sattuu. Paras suositus on istua alas. Varmistakaa turvallisuutenne vuoksi, ettei lähistöllä ole teräviä esineitä.
palataan kokoelma-objekti-esimerkkiin. Muistaa: on lookup menetelmä thatcaches viimeinen lookup toiveissa nopeuttaa tulevia lookups.
todetaanpa se, mikä lienee itsestään selvää: oletetaan, että hakumenetelmä ei tee muutoksia mihinkään kollektio-objektin loogiseen tilaan.
joten … on tullut aika satuttaa sinua. Oletko valmis?
tässä tulee: jos lookup-menetelmä ei tee muutoksia mihinkään kokoelma-objektin loogiseen tilaan, mutta se muuttaa kokoelma-objektin fysikaalista tilaa (se tekee hyvin todellisen muutoksen hyvin todelliseen välimuistiin), pitäisikö lookup-menetelmän olla const
?
vastaus on kaikuva Kyllä. (Jokaiseen sääntöön on poikkeuksia, joten “kyllä” pitäisi oikeastaan olla asteriski vieressä,mutta valtaosan ajasta vastaus on kyllä.)
tässä on kyse “loogisesta const
” yli ” fyysisestä const
.”Se tarkoittaa sitä, että päätös siitä, koristetaanko ametodi const
: llä, riippuu ensisijaisesti siitä, jättääkö tämä menetelmä loogisen tilan muuttumattomaksi riippumatta (istutko?) (haluat ehkä istua alas) riippumatta siitä, tapahtuuko menetelmä todella muuttaa esineen hyvin todellista fyysistä tilaa.
jos se ei uponnut, tai jos et ole vielä tuskissasi, kiusataan se erilleen kahdeksi tapaukseksi:
- jos metodi muuttaa jotakin olion loogisen tilan osaa, se on loogisesti mutaattori; sen ei pitäisi olla
const
evenif (kuten todellisuudessa tapahtuu!) menetelmä ei muuta mitään kappaleen konkreettisen tilan fysikaalisia palasia. - kääntäen menetelmä on loogisesti tarkastaja ja sen tulisi olla
const
, jos se ei koskaan muuta mitään olion slogista tilaa, vaikka (kuten itse asiassa tapahtuu!) menetelmä muuttaa kappaleen konkreettisen tilan fysikaalisia bittejä.
jos olet hämmentynyt, lue se uudelleen.
jos et ole hämmentynyt vaan vihainen, hyvä: et ehkä pidä siitä vielä, mutta ainakin ymmärrät sen. Vedä syvään henkeä ja toista perässäni: “const
ness of a method should have sense from outside the object.”
jos olet vielä vihainen, toista tämä kolme kertaa: “menetelmän pysyvyyden täytyy olla järkevää olion käyttäjille, ja nämä käyttäjät voivat nähdä vain olion loogisen tilan.”
jos olet vielä vihainen, sori, se on mitä on. Kestä se ja elä sen kanssa. Kyllä, tulee olemaan poikkeuksia; jokainen sääntö on niiden mukainen. Mutta pääsääntöisesti tämä looginen const
käsite on hyväksi sinulle ja ohjelmistollesi.
vielä yksi asia. Tämä menee järjettömäksi, mutta ollaan tarkkoja siitä, muuttaako menetelmä kohteen logiikkatilaa. Jos olet luokan ulkopuolella-olet normaali käyttäjä, jokainen kokeilu voit suorittaa (jokainen menetelmä taiseuranta menetelmiä soitat) olisi samat tulokset (samat palautusarvot, samat poikkeukset tai puute poikkeuksia)riippumatta siitä, onko ensin kutsutaan että lookup menetelmä. Jos lookup toiminto muutti mitään tulevaa käyttäytymistä ofether future method (ei vain tehdä siitä nopeampi, mutta muutti lopputulosta, muutti return arvo, muutti theexception), niin lookup menetelmä muutti objektin looginen tila — se on mutuator. Mutta jos hakumenetelmä ei muuttanut mitään muuta kuin ehkä nopeutti joitakin asioita, niin se on tarkastaja.
mitä teen, jos haluan konst-jäsenfunktion tekevän “näkymättömän” muutoksen datajäseneen?
käytä mutable
(tai viimeisenä keinona käytä const_cast
).
pieni osa tarkastajista joutuu tekemään kohteen fysikaaliseen tilaan muutoksia, joita ulkoiset käyttäjät eivät voi havaita — muutoksia fyysiseen mutta ei loogiseen tilaan.
esimerkiksi aiemmin käsitelty kokoelma-esine teki viimeisen hakunsa välimuistiin toivoen parantavansa seuraavan hakunsa suorituskykyä. Koska tässä esimerkissä välimuistia ei voi suoraan havaita millään kokoelma-objektin julkisella käyttöliittymällä (lukuun ottamatta ajoitusta), sen olemassaolo ja tila eivät ole osa objektin epäloogista tilaa, joten sen muutokset ovat näkymättömiä ulkopuolisille käyttäjille. Hakumenetelmä on tarkastaja, koska se ei koskaan muuta kohteen loogista tilaa, riippumatta siitä, että ainakin nykyisessä toteutuksessa se muuttaa kohteen fyysistä tilaa.
kun menetelmät muuttavat fysikaalista mutta ei loogista tilaa, menetelmä on yleensä merkittävä arvolla const
, koska se on todellisuudessa tarkastajamenetelmä. Tästä syntyy ongelma: kun Kääntäjä näkee const
— menetelmäsi muuttavan this
– objektin fyysistä tilaa, se valittaa-se antaa koodillesi virheilmoituksen.
C++ – kääntäjäkieli käyttää mutable
– hakusanaa, joka auttaa omaksumaan tämän loogisen const
käsitteen. Tällöin välimuisti merkittäisiin mutable
– hakusanalla, jolloin kääntäjä tietää, että se saa muuttuaconst
– menetelmän sisällä tai minkä tahansa muun const
osoittimen tai viitteen kautta. Meidän lingossamme mutable
avainsana merkitsee ne kappaleen fysikaalisen tilan osat, jotka eivät kuulu loogiseen tilaan.
mutable
hakusana menee juuri ennen datajäsenen ilmoitusta, eli samaan paikkaan, johon voisi laittaa const
. Toinen lähestymistapa, jota ei suosita, on heittää pois const
‘Ness this
– osoitin, todennäköisesticonst_cast
– hakusanan kautta:
Set* self = const_cast<Set*>(this); // See the NOTE below before doing this!
tämän rivin jälkeen self
on samat bitit kuin this
, eli self == this
, mutta self
on Set*
eikäconst Set*
(teknisesti this
on const Set* const
, mutta oikeimmilla const
ei ole merkitystä tässä keskustelussa).Tämä tarkoittaa, että self
: n avulla voidaan muokata kohdetta, johon this
viittaa.
huomaa: on erittäin epätodennäköinen virhe, joka voi tapahtua const_cast
. Se tapahtuu vain, kun kolme hyvin harvinaista on yhdistetty samaan aikaan: datajäsen, jonka pitäisi olla mutable
(kuten edellä on käsitelty), koostaja, joka ei tue mutable
-hakusanaa ja/tai ohjelmoija, joka ei käytä sitä, ja olio, joka oli alun perin määritelty const
: ksi (toisin kuin normaali, ei-const
olio, johon osoitin-to – const
osoittaa).Vaikka tämä yhdistelmä on niin harvinainen, että se ei ehkä koskaan tapahdu sinulle, jos se koskaan tapahtui, koodi ei välttämättä toimi (standardissa sanotaan, että käyttäytyminen on määrittelemätön).
jos haluat joskus käyttää const_cast
, käytä mutable
. Toisin sanoen, jos sinun on joskus vaihdettava anobject: n jäsentä, ja tämä kohde osoitetaan osoittimella-to-const
, turvallisinta ja yksinkertaisinta on lisätä mutable
jäsenen ilmoitukseen. Voit käyttää const_cast
jos olet varma, että todellinen objekti ei ole const
(esim.Jos olet varma, että objekti on julistettu jokseenkin näin: Set
s;
), mutta jos objekti itse saattaa olla const
(esim. jos se voidaan julistaa kuten: const Set s;
), käytä mutable
eikä const_cast
.
Please Don ‘ t write saying version X of compiler Y on machine Z lets you change a non-mutable
member of aconst
object. En välitä — se on laitonta kielen mukaan ja koodi todennäköisesti epäonnistuu eri Kääntäjä tai jopa eri versio (päivitys) saman kääntäjän. Sano vain ei. Käytä sen sijaan mutable
. Kirjoita codethat on taattu toimimaan, ei koodia, joka ei näytä hajoavan.
tarkoittaako const_cast menetettyjä optimointimahdollisuuksia?
teoriassa Kyllä, käytännössä ei.
vaikka kieli kiellettäisiin const_cast
, ainoa tapa välttää rekisterivälimuistin huuhteleminen const
jäsentoimintapuhelussa olisi aliasing-ongelman ratkaiseminen (ts., osoittamaan, että ei ole olemassa ei-const
osoittimia, jotka osoittaisivat objektin). Tämä voi tapahtua vain harvoissa tapauksissa (kun objekti on konstruoitu const
jäsenfunktion invokaation piiriin, ja kun kaikki ei-const
jäsenfunktion invokaatiot objektin Konstruktion jaconst
jäsenfunktion invokaation välillä ovat staattisesti sidottuja, ja kun jokainen näistä invokaatioista on myös inline
d, ja kun konstruktori itse on inline
d, ja kun jokin jäsenfunktion kutsut ovat inline
).
miksi kääntäjä sallii minun muuttaa int: tä osoitettuani sitä konst int*: lla?
koska “const int* p
“tarkoittaa” p
lupaa olla muuttamatta *p
, “ei” *p
lupaa olla muuttamatta.”
aiheuttaa const int*
pisteen int
ei const
-ify int
. int
ei voi muuttaaconst int*
kautta, mutta jos jollain muulla on int*
(Huom: nro const
), joka viittaa (“peitenimet”) samaan int
, niinint*
voidaan muuttaa int
. Esimerkiksi:
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!) // ...}
huomaa, että main()
ja f(const int*,int*)
voivat olla eri koosteyksiköissä, jotka kootaan eri viikonpäivinä. Tällöin kääntäjä ei voi mitenkään havaita aliasausta käännösaikaan. Siksi emme mitenkään voisi laatia kielisääntöä, joka kieltäisi tämänkaltaiset asiat. Itse asiassa, emme edes haluaisi tehdä tällaista arule, koska yleensä sitä pidetään ominaisuus, että voit olla monia osoittimia osoittaa samaan asiaan. Tosiasia, että yksi näistä osoittimet lupaa olla muuttamatta taustalla “asia” on vain lupaus osoittimen; se ei ole “olion”lupaus.
tarkoittaako “const Fred * p” sitä, että *p ei voi muuttua?
Ei! (Tämä liittyy UKK: hon, joka koskee int
osoittimien aliasentamista.)
“const Fred* p
” tarkoittaa, että Fred
: ää ei voi muuttaa osoittimen p
kautta, mutta voi olla muitakin tapoja päästä theobject: iin menemättä const
: n läpi (kuten aliasetettu ei-const
osoitin, kuten Fred*
). Esimerkiksi jos sinulla on kaksi osoitinta “const Fred* p
” ja “Fred* q
“, jotka osoittavat saman Fred
objektin (aliasing), osoitinta q
voidaan käyttää Fred
objektin muuttamiseen, mutta osoitinta p
ei voi.
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 // ...}
Miksi minulle tulee virhe muunnettaessa Foo** → const Foo**?
, koska muunnos Foo**
→ const Foo**
olisi virheellinen ja vaarallinen.
C++ sallii (turvallisen) muunnoksen Foo*
→ Foo const*
, mutta antaa virheen, jos yrittää implisiittisesti muuntaa Foo**
→const Foo**
.
perustelut sille, miksi kyseinen virhe on hyvä asia, on esitetty alla. Mutta ensin, tässä on yleisin ratkaisu: yksinkertainen muutos const Foo**
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* // ...}
syy, miksi muunnos Foo**
→ const Foo**
on vaarallinen, on se, että se antaisi sinun äänettömästi ja vahingossa muuttaa const Foo
kohdetta ilman valua:
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!! // ...}
jos q = &p
viiva olisi laillinen, q
osoittaisi p
. Seuraava rivi, *q = &x
, muuttuu p
itse (koska *q
on p
) pisteeseen x
. Se olisi huono asia, sillä olisimme hävinneet const
karsinnan: p
on Foo*
, muttax
on const Foo
. p->modify()
rivi hyödyntää p
kykyä muokata referenttiään, mikä on todellinen ongelma, sillä päädyimme muokkaamaan const Foo
.
analogisesti, jos rikollisen piilottaa laillisen valeasun alle, hän voi sitten käyttää hyväkseen valeasulle annettua luottamusta.Huono juttu.
onneksi C++ estää tämän: C++ – kääntäjä on merkinnyt rivin q = &p
käännösaikavirheeksi. Muistutus: älä osoitin-heittää tiesi ympäri, että compile-time Virheilmoitus. Sano Vain Ei!
(Huom.tämän ja Derived**
: n muuntamistaBase**
koskevan kiellon välillä on merkityssisällön samankaltaisuus.)