The Incredible Const Reference That Isn’ t Const
under arbetet med NamedType-biblioteket kom jag över en situation som lämnade mig bedövad i förvirring: en const-referens som tillåter modifiering av objektet det hänvisar till. Utan en const_cast
. Utan en mutable
. Utan något i ärmen.
Hur kan detta vara? Och hur man verkställer const
i den const referensen?
en const-referens som inte är const
här är en beskrivning av en situation där denna konstiga referens kommer upp. Tänk på följande omslagsklass:
template<typename T>class Wrapper{public: Wrapper(T const& value) : value_(value) {} T const& get() const { return value_; } private: T value_;};
denna Wrapper
– klass lagrar ett värde av typen T
och ger åtkomst till det via get()
– metoden (oberoende av det, om du undrar om “get” är ett bra namn, kanske du vill läsa den här detaljerade diskussionen). Den viktiga delen är att metoden get()
bara ger en skrivskyddad åtkomst till det lagrade värdet, eftersom det returnerar en const-referens.
för att kontrollera det, låt oss instantiera mallen med int
, till exempel. Att försöka ändra det lagrade värdet genom get()
– metoden misslyckas med att kompilera (försök att klicka på knappen Kör nedan).
vilket är bara bra, för vi vill att get()
ska vara skrivskyddad.
men låt oss nu instantiera Wrapper
med int&
. Ett användningsfall för detta kan vara att undvika en kopia, eller hålla reda på potentiella ändringar av a
till exempel. Låt oss nu försöka ändra det lagrade värdet genom const-referensen som returneras av get()
:
och om du träffar körknappen ser du att … det sammanställer! Och det skriver ut värdet modifierat genom const-referensen, som om inget speciellt hade hänt.
är inte detta förbryllande? Känner du inte det lugnande intrycket av säkerhet som const
kollapsar runt oss? Vem kan vi lita på?
låt oss försöka förstå vad som händer med denna referens.
Const referens eller referens till const?
kärnan i saken är att vår a-referens är en const-referens, men inte en hänvisning till const.
för att undersand vad det betyder, låt oss sönderdela steg för steg vad som händer i mallen instantiation.
metoden get()
returnerar en const T&
, med T
som kommer från template T
. I vårt andra fall är T
int&
, så const T&
är const (int&) &
.
Låt oss ta en närmare titt på detta const (int&)
. int&
är en referens som hänvisar till en int
och får ändra den int
. Men const (int&)
är en referens int&
som är const
, vilket innebär att själva referensen inte kan ändras.
vad betyder det att ändra en referens, till att börja med? I teorin skulle det innebära att det hänvisar till ett annat objekt eller inte hänvisar till någonting. Men båda dessa möjligheter är olagliga i C++: en referens kan inte återbindas. Det hänvisar till samma objekt under hela livet.
så att vara const
säger inte mycket för en referens, eftersom de alltid är const
, eftersom de inte kan återbinda. Detta innebär att const (int&)
faktiskt är samma typ som int&
.
som lämnar oss med get()
återvänder (int&) &
. Och genom referensreglerna kollapsar är detta int&
.
så get
returnerar en int&
. Dess gränssnitt säger T const&
, men det är faktiskt en int&
. Inte riktigt uttrycksfull kod, är det?
hur man gör const reference en referens till const
nu när vi är förbi det första steget att förstå problemet, hur kan vi fixa det? Det vill säga hur kan vi göra get()
returnera en referens till det lagrade värdet som inte tillåter att ändra det?
ett sätt att göra detta är att uttryckligen infoga en const
inuti referensen. Vi kan göra detta genom att ta bort referensen, lägga till en const
och sätta tillbaka referensen. För att ta bort referensen använder vi std::remove_reference
i C++11, eller den mer praktiska std::remove_reference_t
i C++14:
som du kan se om du klickar på Kör visas det förväntade kompileringsfelet nu när du försöker ändra värdet lagrat i Wrapper
.
jag tycker att det är bra att förstå vad som händer med denna referens, eftersom det är kod som ser ut som det är säkert, men är verkligen inte, och detta är den farligaste typen.
du kan leka med koden i ovanstående lekplatser inbäddade på sidan. Och ett stort tack till stack overflow användaren rakete1111 för att hjälpa mig att förstå orsakerna bakom denna fråga.
du kanske också gillar
- den mest irriterande Parsen: hur man upptäcker det och fixar det snabbt