The Incredible Const Reference That Isn’ t Const
tijdens het werken aan de NamedType bibliotheek stuitte ik op een situatie die me verbijsterd in verbijstering: een const referentie die modificatie van het object dat het verwijst naar mogelijk maakt. Zonder een const_cast
. Zonder een mutable
. Zonder iets achter de hand te hebben.
Hoe kan dit? En hoe moet de const
in die const-referentie worden afgedwongen?
een const referentie die niet const
is hier is een beschrijving van een situatie waarin deze vreemde referentie naar voren komt. Overweeg de volgende wrapper klasse:
template<typename T>class Wrapper{public: Wrapper(T const& value) : value_(value) {} T const& get() const { return value_; } private: T value_;};
deze Wrapper
klasse slaat een waarde op van het type T
, en geeft er toegang toe via de get()
methode (onafhankelijk daarvan, als je je afvraagt of “get” een goede naam is, zou je deze gedetailleerde discussie willen lezen). Het belangrijkste is dat de get()
methode alleen een alleen-lezen toegang geeft tot de opgeslagen waarde, omdat het een const referentie retourneert.
om dat te controleren, laten we het sjabloon bijvoorbeeld instantieren met int
. Proberen om de opgeslagen waarde te wijzigen via de get()
methode mislukt om te compileren (probeer te klikken op de knop Uitvoeren hieronder).
dat is prima, want we willen dat get()
alleen-lezen is.
maar laten we nu Wrapper
instantiseren met int&
. Een voorbeeld hiervan is het vermijden van een kopie, of het bijhouden van mogelijke wijzigingen van a
bijvoorbeeld. Laten we nu proberen om de opgeslagen waarde te wijzigen door middel van de const referentie geretourneerd door get()
:
en als je op de RUN knop drukt zul je zien dat… het compileert! En het drukt de gewijzigde waarde af via de const referentie, alsof er niets bijzonders was gebeurd.
is dit niet verbijsterend? Voelt u niet de geruststellende indruk van veiligheid als const
om ons heen instort? Wie kunnen we vertrouwen?
laten we proberen te begrijpen wat er aan de hand is met deze referentie.
Const verwijzing of verwijzing naar const?
de kern van de zaak is dat onze a-verwijzing een const-verwijzing is, maar geen verwijzing naar const.
naar unders en wat dat betekent, laten we stap voor stap ontbinden wat er gebeurt in de template instantiation.
de methode get()
geeft een const T&
terug, waarbij T
afkomstig is van template T
. In ons tweede geval is T
int&
, dus const T&
is const (int&) &
.
laten we dit eens nader bekijken const (int&)
. int&
is een verwijzing die verwijst naar een int
en die int
mag wijzigen. Maar const (int&)
is een referentie int&
die const
is, wat betekent dat de referentie zelf niet kan worden gewijzigd.
wat betekent het om te beginnen een verwijzing te wijzigen? In theorie zou het betekenen dat het verwijst naar een ander object, of niet verwijzen naar iets. Maar beide mogelijkheden zijn illegaal in C++: een referentie kan niet rebinden. Het verwijst naar hetzelfde object gedurende de hele loop van het leven.
dus const
betekent niet veel voor een referentie, omdat ze altijd const
zijn, omdat ze niet kunnen rebinden. Dit betekent dat const (int&)
in feite hetzelfde type is als int&
.
wat ons overlaat met get()
geeft (int&) &
terug. En door de regels van referenties die instorten, is dit int&
.
So get
geeft een int&
terug. De interface zegt T const&
, maar het is in feite een int&
. Niet echt expressieve code, hè?
Hoe maak je de const verwijzing een verwijzing naar const
nu we voorbij de eerste stap van het begrijpen van het probleem, hoe kunnen we het oplossen? Dat wil zeggen, hoe kunnen we get()
een verwijzing naar de opgeslagen waarde laten retourneren die niet toestaat deze te wijzigen?
een manier om dit te doen is door expliciet een const
binnen de referentie in te voegen. We kunnen dit doen door de referentie af te halen, een const
toe te voegen en de referentie terug te zetten. Om de referentie te verwijderen, gebruiken we std::remove_reference
in C++11, of de handiger std::remove_reference_t
in C++14:
zoals u kunt zien als u op uitvoeren klikt, verschijnt de verwachte compilatiefout nu bij het wijzigen van de waarde opgeslagen in Wrapper
.
ik denk dat het nuttig is om te begrijpen wat er aan de hand is met deze referentie, omdat dit code is die eruit ziet alsof het veilig is, maar echt niet, en dit is de meest gevaarlijke soort.
u kunt spelen met de code in de bovenstaande speeltuinen ingebed in de pagina. En een grote dank aan stack overflow gebruiker rakete1111 voor het helpen me begrijpen van de redenen achter dit probleem.
misschien vindt u
- de meest vervelende Parse: hoe het te herkennen en snel te repareren