Standard C++

Const korrekthet

Vad är “const korrekthet”?

en bra sak. Det betyder att använda nyckelordet const för att förhindra att const objekt blir muterade.

om du till exempel vill skapa en funktion f() som accepterade en std::string, plus att du vill lova att inte ändra uppringarens std::string som skickas till f(), kan du få f() ta emot sin std::string parameter…

  • void f1(const std::string& s); // passera genom referens-till-const
  • void f2(const std::string* sptr); // passera genom pekare-till-const
  • void f3(std::string s); // Pass by value

i pass by reference-to – const och pass by pointer-to-const fall, alla försök att ändra uppringarensstd::string inom funktionerna f() skulle flaggas av kompilatorn som ett fel vid kompilering. Denna kontroll är helt klar vid kompileringstid: det finns inget körtidsutrymme eller hastighetskostnad för const. I fallet pass by value (f3()) får den uppringda funktionen en kopia av uppringarens std::string. Detta innebär att f3() kan ändra sin localcopy, men kopian förstörs när f3() returnerar. I synnerhet kan f3() inte ändra uppringarens std::string – objekt.

som ett motsatt exempel, anta att du ville skapa en funktion g() som accepterade ett std::string, men du vill låta ringer veta att g() kan ändra uppringarens std::string objekt. I det här fallet kan du fåg() att få sin std::string – parameter…

  • void g1(std::string& s); // passera genom referens-till-icke-const
  • void g2(std::string* sptr); // passera genom pekare-till-icke-const

bristen på const i dessa funktioner berättar för kompilatorn att de får (men inte krävs) ändra callers std::string – objekt. Således kan de skicka sina std::string till någon av funktionerna f(), men endast f3() (den som tar emot sin parameter “efter värde”) kan skicka sin std::string till g1()eller g2(). Om f1()eller f2() behöver anropa funktionen g(), måste en lokal kopia av objektet std::string skickas till funktionen g(); parametern till f1() eller f2() kan inte skickas direkt till funktionen g(). T. ex.,

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}

naturligtvis i ovanstående fall görs alla ändringar som g1() gör till objektet localCopysom är lokalt till f1(). i synnerhet kommer inga ändringar att göras till parametern constsom skickades med hänvisning till f1().

hur är “const korrekthet” relaterad till vanlig typ säkerhet?

att deklarera const-ness för en parameter är bara en annan typ av säkerhet.

om du hittar vanlig typ säkerhet hjälper dig att få system korrekt (det gör, särskilt i stora system), hittar duconst korrekthet hjälper också.

fördelen med const korrekthet är att det hindrar dig från att oavsiktligt ändra något du inte förväntade dig skulle ändras. Du hamnar behöva dekorera din kod med några extra tangenttryckningar (const sökord), med the benefit att du berättar kompilatorn och andra programmerare några ytterligare bit av viktig semantisk information— information som kompilatorn använder för att förhindra misstag och andra programmerare använder som dokumentation.

konceptuellt kan du föreställa dig att const std::string till exempel är en annan klass än vanlig std::string, eftersom const-varianten begreppsmässigt saknar de olika mutativa operationerna som finns i varianten icke – const. Du kan till exempel konceptuellt föreställa dig att en const std::string helt enkelt inte har en uppdragsoperatör+= eller andra mutativa operationer.

ska jag försöka få saker att rätta “förr”eller ” senare”?

i början.

Back-patching const korrekthet resulterar i en snöbollseffekt: varje const du lägger till “hit” kräver fyra mer för att läggas till “där borta.”

Lägg till const tidigt och ofta.

vad betyder” const X* p”?

det betyder p pekar på ett objekt i klassen X, men p kan inte användas för att ändra det X objektet (naturligtvis p kan också vara NULL).

Läs det höger till vänster: “p är en pekare till ett X som är konstant.”

till exempel, om klass X har en const medlemsfunktion som inspect() const, är det okej att sägap->inspect(). Men om klass X har en icke – const medlemsfunktion som heter mutate(), är det anerror om du säger p->mutate().

signifikant fångas detta fel av kompilatorn vid kompileringstid-inga körtidstester görs. Det betyder att const inte saktar ner ditt program och kräver inte att du skriver extra testfall för att kontrollera saker vid körning-thecompiler gör arbetet vid kompileringstid.

vad är skillnaden mellan” const X* p”,” X* const p “och”const X* const p”?

Läs pekardeklarationerna från höger till vänster.

  • const X* p betyder ” p pekar på en X som är const“: objektet X kan inte ändras viap.
  • X* const p betyder ” p är en const pekare till en X som inte ärconst“: du kan inte ändra pekaren psjälv, men du kan ändra objektet X via p.
  • const X* const p betyder “p är en const pekare till en X som är const“: du kan inte ändra pekaren psjälv, och du kan inte heller ändra objektet X via p.

och, åh ja, nämnde jag att läsa dina pekardeklarationer höger till vänster?

vad betyder” const X& x”?

det betyder x Alias ett X objekt, men du kan inte ändra det X objekt via x.

Läs det från höger till vänster: “x är en hänvisning till en X som är const.”

till exempel, om klass X har en const medlemsfunktion som inspect() const, är det okej att sägax.inspect(). Men om klass X har en icke – const medlemsfunktion som heter mutate(), är det ett felom du säger x.mutate().

Detta är helt symmetriskt med pekare till const, inklusive det faktum att kompilatorn gör all kontroll vid kompileringstid, vilket innebär att const inte saktar ner ditt program och kräver inte att du skriver extra testfall för att kontrollera saker vid körning.

vad betyder” X const& x “och” X const* p”?

X const& x motsvarar const X& x och X const* xmotsvararconst X* x.

vissa människor föredrar const-på rätt stil, kallar den” konsekvent const “eller, med hjälp av en term som myntades av Simon Brand,” East const.”Faktum är att” Öst const ” – stilen kan vara mer konsekvent än alternativet: “öst const” – stilen sätter alltid const till höger om vad den bekräftar, medan den andra stilen ibland sätter const till vänster och ibland till höger (för const pekardeklarationer och const medlemsfunktioner).

med stilen “öst const” definieras en lokal variabel som är const med consttill höger:int const a = 42;. På samma sätt definieras en static variabel som är const som static double const x = 3.14;.I grund och botten hamnar varje const till höger om det som det bekräftar, inklusive const som krävs för att vara till höger: const pekardeklarationer och med en const medlemsfunktion.

“East const” – stilen är också mindre förvirrande när den används med typalias: Varför har foo och bar olika typer här?

using X_ptr = X*;const X_ptr foo;const X* bar;

med hjälp av” East const ” stil gör detta tydligare:

using X_ptr = X*;X_ptr const foo;X* const foobar;X const* bar;

det är tydligare här att foo och foobar är samma typ och att bar är en annan typ.

stilen “öst const” överensstämmer också med pekardeklarationer. Kontrast den traditionella stilen:

const X** foo;const X* const* bar;const X* const* const baz;

med” East const ” – stilen

X const** foo;X const* const* bar;X const* const* const baz;

trots dessa fördelar är const -on-the-right-stilen ännu inte populär, så äldre kod tenderar att ha den traditionella stilen.

har” X& const x ” någon mening?

Nej, det är nonsens.

för att ta reda på vad ovanstående deklaration betyder, läs den höger till vänster: “x är en consthänvisning till en X“. Men det är överflödigt — referenser är alltid const, i den meningen att du aldrig kan återställa areference för att få det att hänvisa till ett annat objekt. Aldrig. Med eller utan const.

med andra ord är “X& const x” funktionellt ekvivalent med “X& x“. Eftersom du inte får något genom att lägga tillconst efter &, bör du inte lägga till det: det kommer att förvirra människor — const kommer att få vissa människor att tro det X är const, som om du hade sagt “const X& x“.

Vad är en “const medlem funktion”?

en medlemsfunktion som inspekterar (snarare än muterar) sitt objekt.

en const medlemsfunktion indikeras av ett const suffix strax efter medlemsfunktionens parameterlista. Medlemsfunktioner med const suffix kallas “const medlemsfunktioner “eller” inspektörer.”Medlemsfunktioner utanconst suffix kallas “icke- const medlemsfunktioner” eller “mutatorer.”

class Fred {public: void inspect() const; // This member promises NOT to change *this void mutate(); // This member function might change *this};void userCode(Fred& changeable, const Fred& unchangeable){ changeable.inspect(); // Okay: doesn't change a changeable object changeable.mutate(); // Okay: changes a changeable object unchangeable.inspect(); // Okay: doesn't change an unchangeable object unchangeable.mutate(); // ERROR: attempt to change unchangeable object}

försöket att ringa unchangeable.mutate() är ett fel som fångats vid kompileringstiden. Det finns inget runtime space eller speedpenalty för const, och du behöver inte skriva testfall för att kontrollera det vid körning.

den efterföljande constinspect() medlemsfunktionen ska användas för att betyda att metoden inte ändrar objektets Abstract (client-visible) tillstånd. Det är något annorlunda än att säga att metoden inte kommer att ändra “raw bits” av theobject ‘ s struct. C++ kompilatorer får inte ta “bitvis” tolkning om de inte kan lösa thealiasing problem, som normalt inte kan lösas (dvs en icke-const alias kan existera som kan ändra tillståndet för objektet). En annan (viktig) insikt från denna aliasing-fråga: att peka på ett objekt med en pekare tillconst garanterar inte att objektet inte kommer att förändras; det lovar bara att objektet inte kommer att ändras via den pekaren.

Vad är förhållandet mellan en retur-by-reference och en const-medlemsfunktion?

om du vill returnera en medlem av ditt this – objekt genom referens från en inspektörsmetod, bör du returnera det med referens-till-const (const X& inspect() const) eller efter värde (X inspect() const).

class Person {public: const std::string& name_good() const; // Right: the caller can't change the Person's name std::string& name_evil() const; // Wrong: the caller can change the Person's name int age() const; // Also right: the caller can't change the Person's age // ...};void myCode(const Person& p) // myCode() promises not to change the Person object...{ p.name_evil() = "Igor"; // But myCode() changed it anyway!!}

den goda nyheten är att kompilatorn ofta kommer att fånga dig om du får fel. I synnerhet om du av misstagåtervänder en medlem av ditt this-objekt med icke-const – referens, till exempel i Person::name_evil()ovan, kommer kompilatornkommer ofta att upptäcka det och ge dig ett kompileringsfel när du sammanställer insidan av, i det här fallet,Person::name_evil().

den dåliga nyheten är att kompilatorn inte alltid kommer att fånga dig: det finns vissa fall där kompilatorn helt enkelt inte kommer att ge dig ett kompileringstid felmeddelande.

översättning: Du måste tänka. Om det skrämmer dig, hitta en annan arbetslinje; “tänk” är inte ett ord med fyra bokstäver.

kom ihåg “const filosofi ” spridda över hela detta avsnitt :en const medlem funktion mustnot ändra (eller tillåta en uppringare att ändra) this objektets logiska tillstånd (AKA abstrakt tillstånd AKA meaningwisestate). Tänk på vad ett objekt betyder, inte hur det implementeras internt. En persons ålder och namn är logiskten del av personen, men personens granne och arbetsgivare är det inte. En inspektörsmetod som returnerar en del av this-objektets logiska / abstrakta / meningsvisa tillstånd får inte returnera en pekare (eller referens) som inte ärconst till den delen,oberoende av om den delen är internt implementerad som en direkt datamedlem fysiskt inbäddad ithis-objektet eller på något annat sätt.

Vad handlar det om “const-overloading”?

const överbelastning hjälper dig att uppnå const korrekthet.

const överbelastning är när du har en inspektormetod och en mutatormetodmed samma namn och samma antal och typer av parametrar. De två distinkta metoderna skiljer sig endast genom attinspektören är const och mutatorn är icke-const.

den vanligaste användningen av const överbelastning är med subscript operator. Du bör i allmänhet försöka använda en avstandard behållarmallar, till exempel std::vector, men om du behöver skapa din egen klass som har en subscriptoperator, här är tumregeln: subscriptoperatörer kommer ofta i par.

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 subscript operator returnerar en const-referens, så kompilatorn kommer att förhindra att uppringare oavsiktligt ändrar/ändrar Fred. Den icke – const subscript operator returnerar en icke – const referens, vilket är ditt sätt att berätta för dina uppringare (och kompilatorn) att dina uppringare får ändra objektet Fred.

när en användare av klassen MyFredList anropar operatören subscript väljer kompilatorn vilken överbelastning som ska anropas baserat på konstansen för deras MyFredList. Om den som ringer har en MyFredList a eller MyFredList& a, kommer a att ringaden icke – const prenumerationsoperatören, och den som ringer kommer att sluta med en icke-const hänvisning till a Fred:

anta till exempel att class Fred har en inspektörsmetod inspect() const och en mutatormetod mutate():

void f(MyFredList& a) // The MyFredList is non-const{ // Okay to call methods that inspect (look but not mutate/change) the Fred at a: Fred x = a; // Doesn't change to the Fred at a: merely makes a copy of that Fred a.inspect(); // Doesn't change to the Fred at a: inspect() const is an inspector-method // Okay to call methods that DO change the Fred at a: Fred y; a = y; // Changes the Fred at a a.mutate(); // Changes the Fred at a: mutate() is a mutator-method}

men om den som ringer har en const MyFredList a eller const MyFredList& a, då a kommer att ringa const subscriptoperator, och den som ringer kommer att sluta med en consthänvisning till en Fred. Detta gör det möjligt för den som ringer att inspektera Fredvid a, men det förhindrar att den som ringer oavsiktligt muterar/ändrar Fred vid 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 överbelastning för subscript – och funcall-operatörer illustreras här, här, här, här och här.

du kan naturligtvis också använda const -överbelastning för andra saker än prenumerationsoperatören.

Hur kan det hjälpa mig att utforma bättre klasser om jag skiljer logiskt tillstånd från fysiskt tillstånd?

eftersom det uppmuntrar dig att utforma dina klasser från utsidan-in snarare än inifrån-ut, vilket i sin tur gör dina klasser och objekt lättare att förstå och använda, mer intuitiva, mindre felbenägna och snabbare. (Okej, det är en liten förenkling. För att förstå alla if och S och men, måste du bara läsa resten av thisanswer!)

Låt oss förstå detta från insidan – du kommer (borde) utforma dina klasser frånutanför, men om du är ny på detta koncept är det lättare att förstå fråninsidan.

på insidan har dina föremål fysiska (eller konkreta eller bitvis) tillstånd. Detta är staten som är lätt för programmerareatt se och förstå; det är staten som skulle vara där om klassen bara var en C-stil struct.

på utsidan har dina objekt användare av din klass, och dessa användare är begränsade till att endast använda public medlemsfunktioner och friends. Dessa externa användare uppfattar också att objektet har tillstånd, till exempel om objektet är av klass Rectangle med metoder width(), height() och area(), skulle dina användare säga att de tre är alla en del av objektets logiska (eller abstrakta eller meningsvisa) tillstånd. För en extern användare har objektet Rectangle faktiskt ett område, även om det området beräknas i farten (t.ex. om metoden area() returnerar produkten av objektets bredd och höjd). I själva verket, och detta är den viktiga punkten, dina användare vet inte och bryr sig inte om hur dugenomföra någon av dessa metoder; dina användare uppfattar fortfarande, ur deras perspektiv, att ditt objekt logiskt har ameaningwise tillstånd av bredd, höjd och område.

area() exemplet visar ett fall där det logiska tillståndet kan innehålla element som inte direkt realiseras ifysiskt tillstånd. Det motsatta är också sant: klasser döljer ibland avsiktligt en del av deras objekts fysiska(konkreta, bitvis) tillstånd från användare — de tillhandahåller avsiktligt inga public medlemsfunktioner ellerfriends som gör det möjligt för användare att läsa eller skriva eller ens veta om detta dolda tillstånd. Det betyder att det finns bitar i objektets fysiska tillstånd som inte har några motsvarande element i objektets logiska tillstånd.

som ett exempel på det senare fallet kan ett samlingsobjekt cacha sin senaste sökning i hopp om att förbättra prestanda för nästa sökning. Denna cache är verkligen en del av objektets fysiska tillstånd, men där är det en interngenomförandedetalj som förmodligen inte kommer att utsättas för användare — det kommer förmodligen inte att vara en del av objektets logiska tillstånd. Berätta vad som är lätt om du tänker från utsidan-in: om samlingsobjektets användare har noway att kontrollera tillståndet för cachen själv, då cachen är transparent, och är inte en del av objektets logicalstate.

bör konstansen i mina offentliga medlemsfunktioner baseras på vad metoden gör med objektets logiska tillstånd eller fysiska tillstånd?

logisk.

det finns inget sätt att göra nästa del lätt. Det kommer att göra ont. Bästa rekommendationen är att sitta ner. Och snälla, för din säkerhet, se till att det inte finns några skarpa redskap i närheten.

Låt oss gå tillbaka till exemplet collection-object. Ihåg: det finns en uppslagsmetod somcachar den senaste sökningen i hopp om att påskynda framtida sökningar.

Låt oss ange vad som förmodligen är uppenbart: anta att uppslagsmetoden inte gör några ändringar i något av thecollection-objektets logiska tillstånd.

så … det är dags att skada dig. Är du redo?

här kommer: om uppslagsmetoden inte gör någon ändring av något av samlingsobjektets logiska tillstånd, men det ändrar samlingsobjektets fysiska tillstånd (det gör en mycket verklig förändring till den mycket verkliga cachen), borde uppslagsmetoden vara const?

svaret är ett rungande ja. (Det finns undantag från varje regel,så “ja” borde verkligen ha en asterisk bredvid den, men den stora delen av tiden är svaret Ja.)

det här handlar om ” logisk const“över” fysisk const.”Det betyder beslutet om huruvida man ska dekorera amethod med const ska hänga främst på om den metoden lämnar det logiska tillståndet oförändrat, oavsett (sitter du ner?) (du kanske vill sitta ner) oavsett om metoden råkar göra mycket verkliga förändringar i objektets mycket verkliga fysiska tillstånd.

om det inte sjönk in, eller om du ännu inte har ont, Låt oss reta det i två fall:

  • om en metod ändrar någon del av objektets logiska tillstånd är det logiskt en mutator; det borde inte vara const evenif (som faktiskt händer!) metoden ändrar inte några fysiska bitar av objektets konkreta tillstånd.
  • omvänt är en metod logiskt en inspektör och bör vara const om den aldrig ändrar någon del av objektets logiska tillstånd, även om (som faktiskt händer!) metoden ändrar fysiska bitar av objektets konkreta tillstånd.

om du är förvirrad, läs den igen.

om du inte är förvirrad men är arg, bra: du kanske inte gillar det ännu, men åtminstone förstår du det. Ta ett djupt andningoch upprepa efter mig: “constness av en metod bör vara meningsfull från utsidan av objektet.”

om du fortfarande är arg, upprepa det här tre gånger: “en metods beständighet måste vara meningsfull för objektets användare, och dessa användare kan bara se objektets logiska tillstånd.”

om du fortfarande är arg, ledsen, det är vad det är. Sug upp det och lev med det. Ja, det kommer att finnas undantag; varje regel har dem. Men som regel är det här logiska const – begreppet bra för dig och bra för din programvara.

en sak till. Detta kommer att bli inane, men låt oss vara exakta om en metod ändrar objektets logicalstate. Om du är utanför klassen — du är en vanlig användare, skulle varje experiment du kan utföra (varje metod eller följd av metoder du ringer) ha samma resultat (samma returvärden, samma undantag eller brist på undantag)oavsett om du först kallade den uppslagsmetoden. Om lookup-funktionen ändrade något framtida beteende av någon framtida metod (inte bara gör det snabbare men ändrade resultatet, ändrade returvärdet, ändrade theexception), ändrade lookup — metoden objektets logiska tillstånd-det är en mutuator. Men om uppslagsmetodenändrade inget annat än att kanske göra vissa saker snabbare, då är det en inspektör.

Vad gör jag om jag vill att en const-medlemsfunktion ska göra en “osynlig” ändring av en datamedlem?

använd mutable(eller, som en sista utväg, använd const_cast).

en liten andel inspektörer behöver göra ändringar i ett objekts fysiska tillstånd som inte kan observeras av externa användare — förändringar i det fysiska men inte logiska tillståndet.

till exempel cachade samlingsobjektet som diskuterades tidigare sin senaste sökning i hopp om att förbättra prestanda för nästa sökning. Eftersom cachen i det här exemplet inte direkt kan observeras av någon del av samlingsobjektets offentliga gränssnitt (annat än timing), är dess existens och tillstånd inte en del av objektets logiska tillstånd, så ändringar av det är osynliga för externa användare. Lookup-metoden är en inspektör eftersom den aldrig ändrar objektets logiska tillstånd, oavsett det faktum att det, åtminstone för den nuvarande implementeringen, ändrar objektets fysiska tillstånd.

när metoder ändrar det fysiska men inte logiska tillståndet bör metoden i allmänhet markeras som const eftersom den verkligen är en inspektörsmetod. Det skapar ett problem: när kompilatorn ser din const — metod som ändrar det fysiska tillståndetav objektet this kommer det att klaga-det kommer att ge din kod ett felmeddelande.

C++ – kompilatorspråket använder nyckelordet mutable för att hjälpa dig att omfamna denna logiska const – uppfattning. I det här fallet skulle du markera cachen med nyckelordetmutable, så vet kompilatorn att det är tillåtet att ändra i en const – metod eller via någon annan const pekare eller referens. I vår lingo markerar nyckelordet mutable de delar av objektets fysiska tillstånd som inte ingår i det logiska tillståndet.

nyckelordetmutablegår precis före datamedlemmens deklaration, det vill säga samma plats där du kan lägga const. Det andra tillvägagångssättet, inte föredraget, är att kasta bort const‘ness av this pekaren, förmodligen via const_cast nyckelordet:

Set* self = const_cast<Set*>(this); // See the NOTE below before doing this!

efter denna rad kommer self att ha samma bitar som this, det vill säga self == this, men self är en Set* snarare än enconst Set* (Tekniskt this är en const Set* const, men den högsta const är irrelevant för denna diskussion).Det betyder att du kan använda self för att ändra objektet pekat med this.

OBS: Det finns ett extremt osannolikt fel som kan uppstå med const_cast. Det händer bara när tre mycket sällsynta saker kombineras samtidigt: en datamedlem som borde vara mutable (som diskuteras ovan), en kompilerare som inte stöder sökordet mutable och/eller en programmerare som inte använder det och ett objekt som ursprungligen definierades vara const (i motsats till ett normalt, icke-const objekt som pekas på av en pekare-till-const).Även om denna kombination är så sällsynt att det aldrig kan hända dig, om det någonsin hände, kanske koden inte fungerar (standarden säger att beteendet är odefinierat).

om du någonsin vill använda const_cast, använd mutable istället. Med andra ord, om du någonsin behöver ändra en medlem av anobject, och det objektet pekas på av en pekare-till-const, är det säkraste och enklaste att göra att lägga till mutable till medlemmens deklaration. Du kan använda const_cast om du är säker på att det faktiska objektet inte är const (t.ex. om du är säker på att objektet deklareras ungefär så här: Set s;), men om själva objektet kan vara const (t. ex. ifit kan deklareras som: const Set s;), använd mutable snarare än const_cast.

skriv inte att säga version X av kompilatorn Y på maskin Z kan du ändra en icke – mutable medlem av ett const objekt. Jag bryr mig inte — det är olagligt enligt språket och din kod kommer förmodligen att misslyckas på en annan kompilator eller till och med en annan version (en uppgradering) av samma kompilator. Säg bara nej. Använd mutable istället. Skriv koddet är garanterat att fungera, inte kod som inte verkar bryta.

betyder const_cast förlorade optimeringsmöjligheter?

i teorin, ja; i praktiken Nej.

även om språket är förbjudet const_cast, är det enda sättet att undvika att spola registercachen över ett const memberfunction-samtal att lösa aliasing-problemet (dvs., för att bevisa att det inte finns några icke-const pekare som pekar påtill objektet). Detta kan bara hända i sällsynta fall (när objektet är konstruerat inom ramen för const memberfunction invocation, och när alla icke-const member function invocations mellan objektets konstruktion ochconst member function invocation är statiskt bundna, och när var och en av dessa invocations också är inlined, och när konstruktören själv är inlined, och när någon medlem fungerar är konstruktorns samtal inline).

Varför tillåter kompilatorn mig att ändra en int efter att jag har pekat på den med en const int*?

eftersom ” const int* p “betyder” p lovar att inte ändra *p, “inte” *p lovar att inte ändra.”

orsakar en const int* att peka på en int inte const – ify den int. int kan inte ändras viaconst int*, men om någon annan har en int* (Obs: Nej const) som pekar på (“Alias”) samma int, då kanint* användas för att ändra int. Exempelvis:

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!) // ...}

Observera att main() och f(const int*,int*) kan vara i olika kompileringsenheter som sammanställs på olika dagarav veckan. I så fall finns det inget sätt kompilatorn kan eventuellt upptäcka aliasing vid kompileringstiden. Därför finns det inget sätt att vi skulle kunna göra en språkregel som förbjuder den här typen av saker. Faktum är att vi inte ens vill göra en sådanregel, eftersom det i allmänhet anses vara en funktion som du kan ha många pekare som pekar på samma sak. Faktumatt en av dessa pekare lovar att inte ändra den underliggande “saken” är bara ett löfte från pekaren; det är inte ett löfte från “saken”.

betyder “const Fred* p” att * p inte kan förändras?

Nej! (Detta är relaterat till FAQ om aliasing av int pekare.)

const Fred* p” betyder att Fred inte kan ändras via pekare p, men det kan finnas andra sätt att komma åt objektet utan att gå igenom en const (till exempel en aliaserad icke-const pekare som en Fred*). Om du till exempel har två pekare “const Fred* p” och “Fred* q” som pekar på samma Fred – objekt (aliasing) kan pekare q användas för att ändra Fred – objektet men pekaren p kan inte.

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 // ...}

Varför får jag ett felmeddelande om att konvertera en foo** bisexuell const Foo**?

eftersom konvertering av Foo** Macau const Foo** skulle vara ogiltigt och farligt.

C++ tillåter den (säkra) konverteringen Foo* 275>, men ger ett fel om du försöker implicit konvertera Foo**const Foo** 7252>.

motiveringen till varför det felet är bra anges nedan. Men först, här är den vanligaste lösningen: simplychange const Foo** till 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* // ...}

anledningen till att konverteringen från Foo** bisexuell const Foo** är farlig är att det skulle låta dig tyst och oavsiktligt ändra ett const Foo objekt utan en gjutning:

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!! // ...}

om q = &p – linjen var laglig skulle qpeka på p. Nästa rad, *q = &x, ändrar p själv (eftersom *q är p) för att peka på x. Det skulle vara en dålig sak, eftersom vi skulle ha förlorat const qualifier: p är en Foo* menx är en const Foo. Linjen p->modify()utnyttjar p: s förmåga att ändra referenten, vilket är det verkliga problemet,eftersom vi slutade ändra en const Foo.

analogt, om du gömmer en brottsling under en laglig förklädnad, kan han sedan utnyttja det förtroende som ges till den förklädnaden.Det är illa.

tack och lov C++ hindrar dig från att göra detta: linjen q = &p flaggas av C++-kompilatorn som en kompileringstidsfel. Påminnelse: vänligen inte pekare-kasta dig runt det kompileringstid felmeddelandet. Säg Bara Nej!

(Obs: Det finns en konceptuell likhet mellan detta och förbudet mot att konvertera Derived** tillBase**.)

Lämna ett svar

Din e-postadress kommer inte publiceras.