Standardní C++
- Const Correctness
- co je to “const correctness”?
- jak souvisí “neustálá korektnost” s bezpečností běžného typu?
- měl bych se pokusit dostat věci const správné “dříve” nebo “později”?
- co znamená “const X * p”?
- jaký je rozdíl mezi “const X * p”, “X* const p” a “const X * const p”?
- co znamená “const X& x”?
- co znamenají “X const& x” a “X const* p”?
- má “X& const x” nějaký smysl?
- co je to “funkce člena const”?
- jaký je vztah mezi návratovou referencí a funkcí člena const?
- jaký je problém s”přetížením”?
- jak mi může pomoci navrhnout lepší třídy, pokud odliším logický stav od fyzického stavu?
- měla by být konstanta mých veřejných funkcí založena na tom, co metoda dělá s logickým stavem objektu nebo fyzickým stavem?
- co mám dělat, když chci, aby funkce const člena provedla “neviditelnou” změnu datového člena?
- znamená const_cast ztracené možnosti optimalizace?
- Proč mi kompilátor umožňuje změnit int poté, co jsem na něj ukázal const int*?
- znamená “const Fred * p”, že *p se nemůže změnit?
- proč se mi zobrazuje chyba při převodu Foo** → const Foo**?
Const Correctness
co je to “const correctness”?
dobrá věc. To znamená, že pomocí klíčového slova const
zabráníte mutaci objektů const
.
například, pokud jste chtěli vytvořit funkci f()
že přijal std::string
, plus chcete slib callersnot změnit volajícího std::string
který je předán f()
, můžete mít f()
přijmout std::string
parametr…
-
void f1(const std::string& s);
// Projít odkazem–const
-
void f2(const std::string* sptr);
// Předat ukazatel–const
-
void f3(std::string s);
// Projít podle hodnoty
V průsmyku odkazem-na-const
a projít tím, ukazatel-do-const
případech, jakékoli pokusy o změnu volajícíhostd::string
v rámci f()
funkce by být označeny jako kompilátor chybu na čas kompilace. Tato kontrola je prováděna výhradně v době kompilace: pro const
není k dispozici žádný prostor za běhu ani rychlost. V případě hodnoty pass by(f3()
) získá volaná funkce kopii std::string
volajícího. To znamená, že f3()
může změnit svou localcopy, ale kopie je zničena, když f3()
vrátí. Zejména f3()
nemůže změnit objekt std::string
volajícího.
Jako opačný příklad, předpokládejme, že jste chtěl vytvořit funkci, g()
že přijal std::string
, ale chcete letcallers víme, že g()
může změnit volajícího std::string
objekt. V tomto případě můžete mít g()
přijmoutstd::string
parametr…
-
void g1(std::string& s);
// Projít odkazem-na-non-const
-
void g2(std::string* sptr);
// Projít tím, ukazatel-do-non-const
nedostatek const
v těchto funkcích říká kompilátoru, že mohou (ale nejsou povinni) změnit před odbavením std::string
objekt. Mohou tak předat své std::string
žádné f()
funkce, ale pouze f3()
(ten, který přijímá parametr “hodnota”) může předat své std::string
g1()
nebo g2()
. Pokud f1()
nebo f2()
třeba volat buď g()
funkce, místní kopii std::string
objekt musí být předán g()
funkce; theparameter f1()
nebo f2()
nelze přímo předány buď g()
funkce. E. g.,
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}
Samozřejmě ve výše uvedeném případě, jakékoliv změny, které g1()
jsou localCopy
objekt, který je místní f1()
.Zejména, žádné změny nebudou provedeny const
parametr, který byl předán jako odkaz na f1()
.
jak souvisí “neustálá korektnost” s bezpečností běžného typu?
deklarování const
-ness parametru je jen další forma bezpečnosti typu.
pokud zjistíte, že běžný typ bezpečnosti vám pomůže získat systémy správné (to dělá; zejména ve velkých systémech), najdeteconst
správnost pomáhá také.
výhodou správnosti const
je to, že vám brání v neúmyslné úpravě něčeho, co jste neočekávali. Budete potřebovat, aby se vyzdobit váš kód s pár stisky kláves (const
klíčové slovo), s thebenefit, že říkáš, kompilátor a další programátoři některé další kus důležité sémantické informace— informace, které kompilátor používá, aby se zabránilo chybám a ostatní programátoři použít jako dokumentaci.
Koncepčně můžete představit, že const std::string
, například, je jiná třída než obyčejné std::string
,protože const
varianta je koncepčně chybí různé mutační operace, které jsou k dispozici v non-const
varianta. Můžete si například koncepčně představit, že const std::string
jednoduše nemá operátor přiřazení+=
nebo jiné mutativní operace.
měl bych se pokusit dostat věci const správné “dříve” nebo “později”?
na samém, velmi, velmi začátku.
Zpět-záplatování const
správnost následek lavinový efekt: každý const
přidat “tady” vyžaduje čtyři moreto být přidáno “.”
přidat const
brzy a často.
co znamená “const X * p”?
to znamená p
ukazuje na objekt třídy X
, ale p
nelze použít ke změně objektu X
(přirozeně p
může také NULL
).
Přečtěte si to zprava doleva: “p je ukazatel na X, který je konstantní.”
pokud má například třída X
členskou funkci const
, například inspect() const
, je v pořádku řícip->inspect()
. Ale pokud třída X
má non – const
členskou funkci nazvanou mutate()
, je to anerror, pokud řeknete p->mutate()
.
významně je tato chyba zachycena kompilátorem v době kompilace — nejsou prováděny žádné testy běhu. To znamená, že const
není zpomalit váš program a nevyžaduje, abyste napsal další testovací případy zkontrolovat, věci na runtime kompilátor dělá práci v compile-time.
jaký je rozdíl mezi “const X * p”, “X* const p” a “const X * const p”?
Přečtěte si prohlášení ukazatele zprava doleva.
-
const X* p
znamená ”p
ukazuje naX
to jeconst
“: objektX
nelze změnit pomocíp
. -
X* const p
znamená ”p
jeconst
ukazatel naX
, který neníconst
“: samotný ukazatelp
nelze změnit, ale objektX
můžete změnit pomocíp
. -
const X* const p
znamená ”p
jeconst
ukazatel naX
, který jeconst
“: nemůžete změnit samotný ukazatelp
, ani nemůžete změnit objektX
pomocíp
.
a, ach ano, zmínil jsem se, že jsem četl vaše prohlášení o ukazateli zprava doleva?
co znamená “const X& x”?
To znamená, x
přezdívky X
objekt, ale nemůžete to změnit X
objekt pomocí x
.
Přečtěte si to zprava doleva: “x
je odkaz na X
, který je const
.”
pokud má například třída X
členskou funkci const
, například inspect() const
, je v pořádku řícix.inspect()
. Ale pokud třída X
má non – const
členskou funkci nazvanou mutate()
, je to chybapokud řeknete x.mutate()
.
To je zcela symetrický s ukazatele na const, včetně skutečnosti, že kompilátor dělá všechnu kontrolu v čase kompilace, což znamená, const
není zpomalit váš program a nevyžaduje, abyste napsal další testovací případy zkontrolovat věci za běhu.
co znamenají “X const& x” a “X const* p”?
X const& x
je ekvivalentní const X& x
a X const* x
je ekvivalentníconst X* x
.
Někteří lidé dávají přednost const
-on-the-správný styl, volat to “v souladu const
” nebo, používat termín razil Simone Značky, “Východ const
.”Styl”východ const
” může být skutečně konzistentnější než alternativa: styl “východ const
” vždy umístí const
napravo od toho, co tvoří, zatímco jiný styl někdy umístí const
nalevo a někdy napravo (pro const
deklarace ukazatele a const
členské funkce).
ve stylu “East const
” je lokální proměnná, která je const
, definována const
vpravo:int const a = 42;
. Podobně proměnná static
, která je const
, je definována jako static double const x = 3.14;
.V podstatě každý const
končí napravo od věci, kterou tvoří, včetně const
, která je povinna býtnapravo: const
deklarace ukazatele a funkce člena const
.
styl “východ const
” je také méně matoucí, když se používá s typovými aliasy: proč mají foo
a bar
různé typy?
using X_ptr = X*;const X_ptr foo;const X* bar;
pomocí stylu “East const
” je to jasnější:
using X_ptr = X*;X_ptr const foo;X* const foobar;X const* bar;
je zde jasnější, že foo
a foobar
jsou stejného typu a že bar
je jiného typu.
styl” East const
” je také více konzistentní s deklaracemi ukazatele. Naopak tradiční styl:
const X** foo;const X* const* bar;const X* const* const baz;
s “Východ const
” styl
X const** foo;X const* const* bar;X const* const* const baz;
i Přes tyto výhody, const
-on-the-správný styl zatím není populární, takže legacy kód má tendenci mít tradiční styl.
má “X& const x” nějaký smysl?
ne, je to nesmysl.
Chcete-li zjistit, co znamená výše uvedené prohlášení, přečtěte si jej zprava doleva: “x
je const
odkaz na X
“. Ale to je zbytečné — odkazy jsou vždy const
v tom smyslu, že nikdy můžete přesaďte odkaz, aby se to odkazovat na jiný objekt. Nikdy. S nebo bez const
.
jinými slovy, “X& const x
” je funkčně ekvivalentní “X& x
“. Od vás získat nic přidánímconst
po &
, neměli byste ji přidat: to bude mást lidi — const
někteří lidé si myslí, že X
const
, jako kdybys řekl, že “const X& x
“.
co je to “funkce člena const”?
členská funkce, která kontroluje (spíše než mutuje) svůj objekt.
funkce člena const
je označena příponou const
těsně za seznamem parametrů funkce člena. Členské funkce s příponou const
se nazývají “const
členské funkce “nebo ” inspektoři”.”Členské funkce bez příponyconst
se nazývají” non – const
členské funkce “nebo” mutátory.”
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}
pokus o volání unchangeable.mutate()
je chyba zachycená v době kompilace. Neexistuje žádný runtime prostor nebo speedpenalty pro const
a nemusíte psát testovací případy, abyste je mohli zkontrolovat za běhu.
funkce trailing const
na inspect()
členu by měla být použita k tomu, aby metoda nezměnila stav objektu (klientsky viditelný). To se mírně liší od toho, že metoda nezmění “surové bity” theobject struct
. Kompilátorům C++ není dovoleno interpretovat” bitovou ” interpretaci, pokud nedokážou vyřešit problém, který normálně nelze vyřešit (tj. může existovat alias ne – const
, který by mohl změnit stav objektu). Další (důležitý) pohled z tohoto problému aliasingu: ukazování na objekt ukazatelem naconst
nezaručuje, že se objekt nezmění; pouze slibuje, že se objekt nezmění tímto ukazatelem.
pokud chcete vrátit člen vašeho objektu this
odkazem z inspekční metody, měli byste jej vrátit pomocí reference-to-const (const X& inspect() const
) nebo hodnotou (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!!}
dobrou zprávou je, že kompilátor vás často chytí, pokud to uděláte špatně. Zejména, pokud jste accidentallyreturn člen vaší this
objekt pomocí non-const
odkaz, jako v Person::name_evil()
výše, compilerwill často detekovat a dát compile-time error při kompilaci vnitřností, v tomto případě,Person::name_evil()
.
špatnou zprávou je, že kompilátor nebude vždy chytit: tam jsou některé případy, kdy kompilátor prostě nebude evergive vám chybová zpráva kompilace-time.
překlad: musíte myslet. Pokud vás to děsí, najděte jinou práci; “myslet” není čtyřpísmenné slovo.
Zapamatujte si filozofii” const
” rozšířenou v této části: funkce člena const
nesmí změnit (nebo povolit volajícímu změnit) logický stav objektu this
(AKA abstraktní stav AKA významwisestate). Přemýšlejte o tom, co objekt znamená, ne o tom, jak je interně implementován. Věk a jméno osoby jsou logickyčást osoby, ale soused a zaměstnavatel osoby nejsou. Inspektor metoda, která vrací část this
objekt je logická / abstraktní / meaningwise stát nesmí vrátit non-const
ukazatel (nebo odkaz) na část,nezávisle na tom, zda tato část je vnitřně realizovány jako přímý údajů-člen fyzicky vložené dothis
objekt, nebo nějaký jiný způsob.
jaký je problém s”přetížením”?
const
přetížení vám pomůže dosáhnout správnosti const
.
const
přetížení je, když máte metodu inspektora a metodu mutátoru se stejným názvem a stejným počtem a typy parametrů. Dvě odlišné metody se liší pouze tím, že inspektor je const
a mutátor je non – const
.
nejběžnější použití přetížení const
je u operátora dolní index. Obecně byste měl zkusit použít jednu z běžného kontejneru šablony, jako jsou std::vector
, ale pokud potřebujete vytvořit vlastní třídu, která má subscriptoperator, tady je pravidlo: index provozovatelé často přicházejí ve dvojicích.
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 // ...};
operátor const
subscript vrací const
– reference, takže kompilátor zabrání volajícím v neúmyslné změně Fred
. Operátor non – const
dolní index vrací odkaz non – const
, což je váš způsob, jak sdělit volajícím (a kompilátoru), že vaši volající mohou upravit objekt Fred
.
když uživatel vaší třídy MyFredList
zavolá operátorovi dolní index, kompilátor vybere, které přetížení má volat na základě stálosti jejich MyFredList
. Pokud volající má MyFredList a
nebo MyFredList& a
, pak a
bude zavolat non-const
dolní index operátor a volající skončí s non-const
odkaz na Fred
:
například, předpokládejme, že class Fred
má inspektor-metoda inspect() const
a mutator-metoda 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}
Nicméně, pokud volající má const MyFredList a
nebo const MyFredList& a
, pak a
volat const
subscriptoperator, a volající skončí s const
odkaz na Fred
. To umožňuje volajícímu zkontrolovat Fred
na a
, ale zabraňuje volajícímu v neúmyslné mutaci / změně Fred
na 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 přetížení pro subscript-a funcall-operátory je znázorněno zde, zde, zde, zde a zde.
můžete samozřejmě také použít const
– přetížení pro jiné věci než operátor dolního indexu.
Protože to podporuje vás navrhnout své třídy z venku-in, spíše než zevnitř-ven, což v turnmakes své třídy a objekty jednodušší pochopit a používat, více intuitivní, méně náchylné k chybám, a rychleji. (Dobře, to je trochu přehnané zjednodušení. Chcete-li pochopit všechny if a je A ale je, budete muset přečíst zbytek thisanswer!)
pojďme to pochopit zevnitř ven-budete (měli) navrhovat své třídy zvenku, ale pokud jste v tomto konceptu noví, je to snazší pochopitside-out.
na vnitřní straně mají vaše objekty fyzický (nebo betonový nebo bitový) stav. To je stav, který je pro programátory snadné vidět a pochopit; je to stav, který by tam byl, kdyby třída byla jen ve stylu C struct
.
na vnější straně mají vaše objekty uživatele vaší třídy A tito uživatelé jsou omezeni na použití pouze public
memberfunctions a friend
s. Tito externí uživatelé také vnímají objekt jako stav, například pokud je objekt třídy Rectangle
s metodami width()
, height()
a area()
, vaši uživatelé by řekli, že tyto tři jsou součástí logického (nebo abstraktního nebo významového) stavu objektu. Pro externího uživatele má objekt Rectangle
ve skutečnosti plochu, i když je tato oblast vypočtena za běhu (např. pokud metoda area()
vrátí součin šířky a výšky objektu). Ve skutečnosti, a to je důležitý bod, vaši uživatelé nevědí a nestarají se o to, jak provádíte některou z těchto metod; vaši uživatelé stále vnímají, z jejich pohledu, že váš objekt má logicky ameaningwise stav šířky, výšky a plochy.
příklad area()
ukazuje případ, kdy logický stav může obsahovat prvky, které nejsou přímo realizovány ve fyzickém stavu. Opak je také pravda: třídy někdy záměrně skrýt část jejich objektů, fyzické(beton, bitwise) státu od uživatelů — jsou záměrně neposkytují žádné public
členské funkce nebofriend
s, který by umožnil uživatelům číst nebo psát, nebo dokonce vědět o tomto skrytém stavu. To znamená, že ve fyzickém stavu objektu nejsou žádné odpovídající prvky v logickém stavu objektu.
jako příklad tohoto posledně uvedeného případu může objekt kolekce ukládat do mezipaměti své poslední vyhledávání v naději, že zlepší výkon svého dalšího vyhledávání. Tato mezipaměť je jistě součástí fyzického stavu objektu, ale tam je to interní implementační detail, který pravděpodobně nebude vystaven uživatelům — pravděpodobně nebude součástí logického stavu objektu. Říkat, co je to, co je snadné, pokud si myslíte, že zvenčí-in: pokud uživatelé objektu kolekce mají nyní možnost zkontrolovat stav samotné mezipaměti, pak je mezipaměť průhledná a není součástí logického stavu objektu.
měla by být konstanta mých veřejných funkcí založena na tom, co metoda dělá s logickým stavem objektu nebo fyzickým stavem?
logické.
neexistuje žádný způsob, jak tuto další část usnadnit. Bude to bolet. Nejlepší doporučení je posadit se. A prosím, pro vaši bezpečnost, ujistěte se, že v okolí nejsou žádné ostré nářadí.
vraťme se k příkladu kolekce-objekt. Pamatovat: existuje metoda vyhledávání, která ukládá poslední vyhledávání v naději, že urychlí budoucí vyhledávání.
uveďme, co je pravděpodobně zřejmé: předpokládejme, že metoda vyhledávání nezmění žádný logický stav objektu collection.
takže … přišel čas ublížit vám. Jste připraveni?
zde přichází: pokud metoda vyhledávání neprovede žádnou změnu logického stavu objektu kolekce, ale změní fyzický stav objektu kolekce (provede velmi skutečnou změnu velmi skutečné mezipaměti), měla by být metoda vyhledávání const
?
odpověď zní hlasitě Ano. (Existují výjimky z každého pravidla, takže “ano” by mělo mít opravdu hvězdičku vedle něj, ale drtivá většina času, odpověď je Ano.)
Jedná se o “logické const
” nad ” fyzické const
.”To znamená, že rozhodnutí o tom, zda ozdobit ametod const
, by mělo záviset především na tom, zda tato metoda ponechává logický stav beze změny, bez ohledu na to(sedíte?) (možná budete chtít sednout) bez ohledu na to, zda se metoda stane, aby se velmi reálnézměny na velmi reálném fyzickém stavu objektu.
V případě, že neměl klesnout, nebo v případě, že ještě nejste v bolesti, pojďme dráždit to od sebe do dvou případech:
- Pokud metoda změny jakékoliv části objektu je logické státu, logicky je mutator; to by nemělo být
const
i (jak se vlastně stane!) metoda nemění žádné fyzické bity konkrétního stavu objektu. - naopak, metoda je logicky inspektor a měla by být
const
, pokud nikdy nezmění žádnou část logického stavu objektu, i když (jak se skutečně děje!) metoda mění fyzické bity konkrétního stavu objektu.
pokud jste zmateni, přečtěte si to znovu.
Pokud nejste zmateni, ale jste naštvaní, dobře: možná se vám to ještě nelíbí, ale alespoň tomu rozumíte. Zhluboka se nadechněte a opakujte po mně: “const
ness metody by měla mít smysl zvenčí objektu.”
pokud jste stále naštvaní, opakujte to třikrát: “stálost metody musí mít smysl pro uživatele objektu a tito uživatelé mohou vidět pouze logický stav objektu.”
pokud jste stále naštvaný, Omlouvám se, je to, co to je. Vysajte to a žijte s tím. Ano, budou výjimky; každé pravidlo je má. Ale zpravidla je tato logická const
představa dobrá pro vás a dobrá pro váš software.
ještě jedna věc. To se dostane inane, ale buďme přesní o tom, zda metoda změní logický stav objektu. Pokud jste mimo třídu — jste normální uživatel, každý experiment by mohl provádět (každý způsob orsequence metod volání) by měla stejné výsledky (stejné návratové hodnoty, stejné výjimky, nebo nedostatek výjimky)bez ohledu na to, zda jste poprvé volal, že vyhledávací metoda. Pokud vyhledávací funkce změnit jakékoli budoucí chování libovolné budoucí způsob (ne jen dělat to rychlejší, ale změnil výsledek, změnil návratová hodnota, změnil výjimkou), pak vyhledávací metoda změnil objekt je logické státu — to je mutuator. Ale pokud metoda vyhledávánízměnil nic jiného, než snad dělat některé věci rychleji, pak je to Inspektor.
použijte mutable
(nebo jako poslední možnost použijte const_cast
).
malé procento inspektorů musí provést změny fyzického stavu objektu, které nemohou být pozorovány externími uživateli-změny fyzického, ale ne logického stavu.
například, kolekce-objekt diskutovali dříve v mezipaměti jeho poslední vyhledávání v naději, že zlepšit výkonnost jeho další vyhledávání. Od cache, v tomto příkladu, nemůže být přímo pozorován část kolekce-objekt je veřejné rozhraní (jiné než načasování), jeho existenci a stát není součástí objektu’slogical stavu, takže změny jsou neviditelné pro externí uživatele. Metoda vyhledávání je Inspektor, protože nikdy nemění logický stav objektu, bez ohledu na to, že alespoň pro současnou implementaci mění fyzický stav objektu.
když metody změní fyzický, ale ne logický stav, metoda by měla být obecně označena jako const
, protože je to skutečně inspekční metoda. To vytváří problém: když kompilátor uvidí vaši metodu const
, která mění fyzický stav objektu this
, bude si stěžovat-dá vašemu kódu chybovou zprávu.
jazyk kompilátoru C++ používá klíčové slovo mutable
, které vám pomůže přijmout tento logický pojem const
. V tomto případě byste označili mezipaměť klíčovým slovem mutable
, takže kompilátor ví, že je povoleno měnit uvnitř metodyconst
nebo pomocí jakéhokoli jiného ukazatele nebo odkazu const
. V našem žargonu Klíčové slovo mutable
označuje ty části fyzického stavu objektu, které nejsou součástí logického stavu.
Klíčové slovo mutable
jde těsně před deklarací datového člena, tj. Druhý přístup, není přednost, je obsazení pryč const
‘ness this
ukazatel, asi přesconst_cast
klíčové slovo:
Set* self = const_cast<Set*>(this); // See the NOTE below before doing this!
Po této linii, self
bude mít stejné kousky jako this
, která je self == this
, ale self
Set*
, nikoliconst Set*
(technicky this
const Set* const
, ale nejvíce vpravo, const
je irelevantní pro tuto diskusi).To znamená, že můžete použít self
k úpravě objektu označeného this
.
poznámka: u const_cast
může dojít k extrémně nepravděpodobné chybě. Stává se to pouze tehdy, když jsou současně kombinovány tři velmi vzácné věci: datový člen, který by měl být mutable
(jako je uvedeno výše), compilerthat nepodporuje mutable
klíčových slov a/nebo programátor, který nechce použít, a objekt, který byl originallydefined const
(oproti normální, non-const
objekt, který je ukazoval ukazatel–const
).I když je tato kombinace tak vzácná, že se vám nemusí nikdy stát, pokud se to někdy stalo, kód nemusí fungovat (standard říká, že chování není definováno).
pokud chcete někdy použít const_cast
, použijte místo toho mutable
. Jinými slovy, pokud budete někdy potřebovat změnit členem objekt a to objekt, na který ukazuje ukazatel–const
, nejbezpečnější a nejjednodušší věc, kterou musíte udělat, je přidat mutable
na členské prohlášení. Můžete použít const_cast
pokud jste si jisti, že skutečný objekt není const
(například, pokud jste si jisti, že objekt je deklarován něco jako toto: Set
s;
), ale pokud samotný objekt může být const
(např. jestli může být deklarován jako: const Set s;
), použijte mutable
než const_cast
.
nepište prosím, že verze X kompilátoru Y na stroji z umožňuje změnit ne – mutable
člen objektuconst
. Je mi to jedno – je to nezákonné podle jazyka a váš kód pravděpodobně selže na jiném kompilátoru nebo dokonce v jiné verzi (upgrade) stejného kompilátoru. Prostě Řekni NE. Místo toho použijte mutable
. Napište kódže je zaručeno, že bude fungovat, ne kód, který se nezdá být zlomený.
znamená const_cast ztracené možnosti optimalizace?
teoreticky ano; v praxi ne.
I když je tento jazyk mimo zákon const_cast
, jediný způsob, jak, aby se zabránilo splachování rejstříku cache přes const
memberfunction hovor bude řešit aliasing problém (tj., dokázat, že neexistují žádné non-const
ukazatele, které ukazují na objekt). To se může stát pouze ve vzácných případech (když objekt je postaven v rozsahu const
memberfunction vyvolání, a když jsou všechny non-const
členské funkce vzývání mezi objektem je stavební aconst
členské funkce vyvolání jsou staticky vázané, a když každý z těchto vzývání je také inline
d, a konstruktor je sám o sobě inline
d, a když žádné členské funkce konstruktoru volání jsou inline
).
Proč mi kompilátor umožňuje změnit int poté, co jsem na něj ukázal const int*?
protože “const int* p
“znamená” p
slibuje, že nezmění *p
, “ne” *p
slibuje, že se nezmění.”
způsobuje, že const int*
ukazuje na int
const
-jestliže int
. int
nemůže být změněn přesconst int*
, ale pokud má někdo jiný int*
(poznámka: ne const
), který odkazuje na (“alias”) stejné int
, pakint*
může být použit pro změnu int
. Příklad:
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!) // ...}
Všimněte si, že main()
a f(const int*,int*)
mohou být v různých kompilačních jednotkách, které jsou sestavovány v různých dnech týdne. V takovém případě neexistuje způsob, jak by kompilátor mohl detekovat aliasing v době kompilace. Proto neexistuje žádný způsob, jak bychom mohli vytvořit jazykové pravidlo, které by něco takového zakazovalo. Ve skutečnosti bychom ani nechtěli dělat takovou arule, protože obecně je to považováno za funkci, kterou můžete mít mnoho ukazatelů ukazujících na stejnou věc. Skutečnost, že jeden z těchto ukazatelů slibuje, že nezmění základní “věc”, je jen slib, který učinil ukazatel; není to příslib “věci”.
znamená “const Fred * p”, že *p se nemůže změnit?
ne! (To souvisí s FAQ o aliasingu int
ukazatelů.)
“const Fred* p
” znamená to, že Fred
nemůže být změněn přes ukazatel p
, ale tam by mohlo být jiné způsoby, jak se dostat na objekt, aniž by přes const
(jako aliasu non-const
ukazatel jako Fred*
). Například, pokud máte dva ukazatele “const Fred* p
” a “Fred* q
“, které odkazují na stejný Fred
objekt (aliasing), ukazatel q
může být použit pro změnu Fred
objekt, ale ukazatel p
nemůže.
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 // ...}
proč se mi zobrazuje chyba při převodu Foo** → const Foo**?
protože převod Foo**
→ const Foo**
by byl neplatný a nebezpečný.
C++ umožňuje (bezpečnou) konverzi Foo*
→ Foo const*
, ale pokud se pokusíte implicitně převést Foo**
→const Foo**
, zobrazí se chyba.
důvody, proč je tato chyba dobrá, jsou uvedeny níže. Ale nejprve je zde nejběžnější řešení: simplychance const Foo**
na const Foo* const*
:
důvod, proč převod z Foo**
→ const Foo**
je nebezpečné, je, že by tě nechal mlčky a accidentallymodify const Foo
objekt bez obsazení:
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!! // ...}
Pokud q = &p
linie byly legální, q
bude ukazovat na p
. Následující řádek, *q = &x
, změní p
sám (protože *q
je p
) na bod x
. To by bylo špatné, protože bychom ztratili kvalifikátor const
: p
je Foo*
, alex
je const Foo
. Řádek p->modify()
využívá schopnost p
upravit svůj referent,což je skutečný problém, protože jsme skončili úpravou const Foo
.
analogicky, pokud skryjete zločince pod zákonným převlekem, může pak využít důvěry dané tomuto převleku.To je špatné.
naštěstí vám v tom C++ brání: řádek q = &p
je kompilátorem c++ označen jako kompilace-timeerror. Připomenutí: neposílejte ukazatel-cast si cestu kolem této kompilace-time chybová zpráva. Prostě Řekni NE!
(Poznámka: existuje koncepční podobnost mezi tímto a zákazem převodu Derived**
naBase**
.)