The Incredible Const Reference That Isn’ t Const
podczas pracy nad biblioteką NamedType natknąłem się na sytuację, która zaskoczyła mnie w oszołomieniu: odniesienie const, które umożliwia modyfikację obiektu, do którego się odnosi. Bez const_cast
. Bez mutable
. Bez niczego w rękawie.
Jak to możliwe? A jak wyegzekwować const
w tym konst.?
a const reference that isn ‘ t const
oto opis sytuacji, w której pojawia się to dziwne odniesienie. Rozważ następującą klasę owijania:
template<typename T>class Wrapper{public: Wrapper(T const& value) : value_(value) {} T const& get() const { return value_; } private: T value_;};
ta klasa Wrapper
przechowuje wartość typu T
i daje do niej dostęp za pomocą metody get()
(niezależnie od tego, jeśli zastanawiasz się, czy “get” jest dobrą nazwą, możesz przeczytać tę szczegółową dyskusję). Ważną częścią jest to, że metoda get()
daje dostęp tylko do odczytu zapisanej wartości, ponieważ zwraca referencję const.
aby to sprawdzić, stwórzmy instancję szablonu na przykład za pomocą int
. Próba modyfikacji zapisanej wartości za pomocą metody get()
nie powiodła się (spróbuj kliknąć przycisk Uruchom poniżej).
co jest w porządku, ponieważ chcemy, aby get()
był tylko do odczytu.
ale teraz stwórzmy Wrapper
z int&
. Jednym z przypadków użycia może być uniknięcie kopii lub śledzenie potencjalnych modyfikacji a
na przykład. Teraz spróbujmy zmodyfikować zapisaną wartość za pomocą referencji const zwracanej przez get()
:
a jak naciśniesz przycisk RUN to zobaczysz, że … to się kompiluje! I wyświetla wartość zmodyfikowaną przez odniesienie const, jakby nic specjalnego się nie stało.
czy to nie zaskakujące? Nie czujesz pocieszającego wrażenia bezpieczeństwa zapewnianego przez const
? Komu możemy zaufać?
spróbujmy zrozumieć, co się dzieje z tym odniesieniem.
const odniesienie czy odniesienie do const?
sedno sprawy polega na tym, że nasz A reference jest odniesieniem const, ale nie odniesieniem do const.
aby zrozumieć, co to oznacza, rozłożmy krok po kroku, co dzieje się w instancji szablonu.
metoda get()
zwraca const T&
, przy czym T
pochodzi z template T
. W naszym drugim przypadku T
to int&
, więc const T&
to const (int&) &
.
przyjrzyjmy się temu bliżej const (int&)
. int&
jest odniesieniem, które odnosi się do int
i może modyfikować tę int
. Ale const (int&)
jest referencją int&
, czyli const
, co oznacza, że sama Referencja nie może być modyfikowana.
co to znaczy zmodyfikować referencję, na początek? W teorii oznaczałoby to odwoływanie się do innego obiektu lub nie odwoływanie się do niczego. Ale obie te możliwości są nielegalne w C++: Referencja nie może ponownie się znaleźć. Odnosi się ona do tego samego obiektu w ciągu całego jego życia.
więc bycie const
nie mówi wiele dla odniesienia, ponieważ zawsze są const
, ponieważ nie mogą się ponownie znaleźć. Oznacza to, że const (int&)
jest tym samym typem co int&
.
co pozostawia nam get()
powrót (int&) &
. A zgodnie z zasadami odsyłaczy to jest int&
.
więc get
zwraca int&
. Jego interfejs mówi T const&
, ale w rzeczywistości jest to int&
. Niezbyt ekspresyjny kod, prawda?
jak sprawić, aby odniesienie do const było odniesieniem do const
teraz, gdy minęliśmy pierwszy krok zrozumienia problemu, jak możemy go naprawić? To znaczy, jak możemy sprawić, że get()
zwróci odwołanie do zapisanej wartości, które nie pozwala na jej modyfikację?
jednym ze sposobów jest jawne wstawienie const
wewnątrz referencji. Możemy to zrobić, usuwając odniesienie ,dodając const
i odkładając odniesienie z powrotem. Aby usunąć odniesienie, używamy std::remove_reference
w C++11 lub wygodniejszego std::remove_reference_t
w C++14:
jak widać po kliknięciu Uruchom, oczekiwany błąd kompilacji pojawia się teraz podczas próby modyfikacji wartości przechowywanej w Wrapper
.
myślę, że warto zrozumieć, co się dzieje z tym odniesieniem, ponieważ jest to kod, który wygląda na bezpieczny, ale tak naprawdę nie jest, a to jest najbardziej niebezpieczny rodzaj.
możesz bawić się kodem na powyższych placach zabaw osadzonych na stronie. I wielkie podziękowania dla użytkownika Stack overflow rakete1111 za pomoc w zrozumieniu przyczyn tego problemu.
Możesz również polubić
- najbardziej irytujący Parse: jak go rozpoznać i szybko naprawić