W poprzedniej lekcji (12.3 -- Odniesienia do wartości), omówiliśmy, w jaki sposób odwołanie do lwartości może wiązać się tylko z modyfikowalną wartością. Oznacza to, że poniższe działania są nielegalne:
int main()
{
const int x { 5 }; // x is a non-modifiable (const) lvalue
int& ref { x }; // error: ref can not bind to non-modifiable lvalue
return 0;
}Jest to niedozwolone, ponieważ umożliwiłoby nam modyfikację zmiennej stałej (x) poprzez odwołanie inne niż stałe (ref).
Ale co, jeśli chcemy mieć zmienną stałą, do której chcemy utworzyć odniesienie? Zwykłe odniesienie do wartości (do wartości niebędącej stałą) nie wystarczy.
Odniesienie do wartości const
Używając słowa kluczowego const podczas deklarowania odniesienia do wartości, informujemy Odniesienie do wartości, aby traktować obiekt, do którego się odwołuje, jako do stałej. Takie odniesienie nazywa się odniesieniem do wartości stałej (czasami nazywany odniesieniem do const lub a odniesieniem do stałej).
Odwołania lwartości do const mogą wiązać się z niemodyfikowalnymi lwartościami:
int main()
{
const int x { 5 }; // x is a non-modifiable lvalue
const int& ref { x }; // okay: ref is a an lvalue reference to a const value
return 0;
}Ponieważ odniesienia lwartości do const traktują obiekt, do którego się odwołują jako const, można ich używać do uzyskiwania dostępu, ale nie do modyfikowania wartości, do której się odwołuje:
#include <iostream>
int main()
{
const int x { 5 }; // x is a non-modifiable lvalue
const int& ref { x }; // okay: ref is a an lvalue reference to a const value
std::cout << ref << '\n'; // okay: we can access the const object
ref = 6; // error: we can not modify an object through a const reference
return 0;
}Inicjowanie odniesienia do wartości do const z modyfikowalną wartością
Odniesienia do wartości do const mogą również wiązać do modyfikowalnych lwartości. W takim przypadku obiekt, do którego się odwołujemy, jest traktowany jako stały, gdy uzyskujemy do niego dostęp poprzez odwołanie (nawet jeśli obiekt bazowy nie jest stały):
#include <iostream>
int main()
{
int x { 5 }; // x is a modifiable lvalue
const int& ref { x }; // okay: we can bind a const reference to a modifiable lvalue
std::cout << ref << '\n'; // okay: we can access the object through our const reference
ref = 7; // error: we can not modify an object through a const reference
x = 6; // okay: x is a modifiable lvalue, we can still modify it through the original identifier
return 0;
}W powyższym programie łączymy stałą referencję ref z modyfikowalną lwartością x. Możemy wtedy użyć ref aby uzyskać dostęp x, ale ponieważ ref jest const, nie możemy modyfikować wartości x do ref. Jednakże nadal możemy modyfikować wartość x bezpośrednio (używając identyfikatora x).
Najlepsza praktyka
Zamawiaj lvalue references to const za lvalue references to non-const chyba że musisz zmodyfikować obiekt, do którego się odwołujesz.
Inicjowanie odniesienia do wartości do const za pomocą rwartości
Być może, co zaskakujące, lwartości odniesienia do const mogą również wiązać się z rwartościami:
#include <iostream>
int main()
{
const int& ref { 5 }; // okay: 5 is an rvalue
std::cout << ref << '\n'; // prints 5
return 0;
}Kiedy tak się stanie, tworzony jest obiekt tymczasowy i inicjalizowany tą wartością, a odwołanie do const jest powiązane z tym obiektem tymczasowym.
Powiązana treść
Omówiliśmy obiekty tymczasowe w lekcji >2.5 — Wprowadzenie do zakresu lokalnego.
Inicjowanie odniesienia do wartości do const wartością innego typu
Odniesienia do wartości stałej mogą nawet wiązać się z wartościami innego typu typ, o ile te wartości można niejawnie przekonwertować na typ referencyjny:
#include <iostream>
int main()
{
// case 1
const double& r1 { 5 }; // temporary double initialized with value 5, r1 binds to temporary
std::cout << r1 << '\n'; // prints 5
// case 2
char c { 'a' };
const int& r2 { c }; // temporary int initialized with value 'a', r2 binds to temporary
std::cout << r2 << '\n'; // prints 97 (since r2 is a reference to int)
return 0;
}W przypadku 1 tworzony jest tymczasowy obiekt typu double i inicjowany wartością int 5. Następnie const double& r1 jest powiązany z tym tymczasowym obiektem double.
W przypadku 2 tymczasowy obiekt typu int jest tworzony i inicjowany wartością char a. Następnie const int& r2 jest powiązany z tym tymczasowym obiektem int.
W obu przypadkach typ odniesienia i typ tymczasowego dopasowania.
Kluczowa informacja
Jeśli spróbujesz powiązać odwołanie do stałej lwartości z wartością innego typu, kompilator utworzy obiekt tymczasowy tego samego typu co odwołanie, zainicjuje go przy użyciu wartości, a następnie powiąże odwołanie z odwołaniem tymczasowym.
Zauważ także, że kiedy print r2 wypisuje jako int, a nie jako char. Dzieje się tak, ponieważ r2 jest odniesieniem do obiektu int (tymczasowego inta, który został utworzony), a nie do char c.
Chociaż zezwolenie na to może wydawać się dziwne, zobaczymy przykłady, gdzie jest to przydatne na lekcji 12.6 -- Przekaż stałą lwartość odniesienia.
Ostrzeżenie
Zwykle zakładamy, że referencja jest identyczna z obiektem, z którym jest powiązana - ale to założenie zostaje złamane, gdy referencja jest związany z tymczasową kopią obiektu lub tymczasowym obiektem powstałym w wyniku konwersji obiektu. Wszelkie modyfikacje dokonane później w oryginalnym obiekcie nie będą widoczne w odniesieniu (ponieważ odnosi się on do innego obiektu) i odwrotnie.
Oto głupi przykład ilustrujący to:
#include <iostream>
int main()
{
short bombs { 1 }; // I can has bomb! (note: type is short)
const int& you { bombs }; // You can has bomb too (note: type is int&)
--bombs; // Bomb all gone
if (you) // You still has?
{
std::cout << "Bombs away! Goodbye, cruel world.\n"; // Para bailar la bomba
}
return 0;
}W powyższym przykładzie bombs jest short i you jest const int&. Ponieważ you można powiązać z int obiektem, gdy you inicjowania za pomocą bombs, kompilator niejawnie przekonwertuje bombs na int, co skutkuje utworzeniem obiektu tymczasowy int obiekt (o wartości 1). you jest ostatecznie powiązany z tym obiektem tymczasowym, a nie bombs.
Po wywołaniu bombs zmniejszany, you nie ma to wpływu, ponieważ odwołuje się do innego obiektu. Zatem chociaż spodziewamy się if (you) będzie oceniane jako false, w rzeczywistości wynosi true.
Gdybyś przestał wysadzać świat w powietrze, byłoby wspaniale.
Odniesienia stałe powiązane z obiektami tymczasowymi wydłużają czas życia obiektu tymczasowego
Obiekty tymczasowe są zwykle niszczone na końcu wyrażenia, w którym zostały utworzone.
Biorąc pod uwagę instrukcję const int& ref { 5 };, zastanów się, co by się stało, gdyby obiekt tymczasowy został utworzony w celu przechowywania rvalue 5 została zniszczona na końcu wyrażenia inicjującego ref. Referencja ref pozostałaby zawieszona (odwołująca się do obiektu, który został zniszczony), a przy próbie uzyskania dostępu ref.
zachowanie byłoby niezdefiniowane.Aby uniknąć w takich przypadkach zawieszonych referencji, w C++ obowiązuje specjalna zasada: Kiedy odwołanie do stałej lwartości jest bezpośrednio powiązane z obiektem tymczasowym, czas życia obiektu tymczasowego jest wydłużany tak, aby odpowiadał czasowi życia obiektu tymczasowego reference.
#include <iostream>
int main()
{
const int& ref { 5 }; // The temporary object holding value 5 has its lifetime extended to match ref
std::cout << ref << '\n'; // Therefore, we can safely use it here
return 0;
} // Both ref and the temporary object die hereW powyższym przykładzie, gdy ref jest inicjowany wartością 5, tworzony jest obiekt tymczasowy i ref jest powiązany z tym obiektem tymczasowym. Czas życia obiektu tymczasowego odpowiada czasowi życia ref. Dzięki temu możemy bezpiecznie wydrukować wartość ref w kolejnym wyciągu. Następnie zarówno ref jak i obiekt tymczasowy wychodzą poza zakres i są niszczone na końcu bloku.
Kluczowa informacja
Odniesienia do wartości lvalue mogą wiązać się tylko z modyfikowalnymi lwartościami.
Odniesienia lwartości do const mogą wiązać się z modyfikowalnymi lwartościami, niemodyfikowalnymi lwartościami i rwartościami. To czyni je znacznie bardziej elastycznym typem odniesienia.
Dla zaawansowanych czytelników
Przedłużenie okresu istnienia działa tylko wtedy, gdy odwołanie stałe jest bezpośrednio powiązane z tymczasowym. Elementy tymczasowe zwrócone przez funkcję (nawet te zwrócone przez odwołanie do stałej) nie kwalifikują się do przedłużenia okresu istnienia.
Pokazujemy przykład tego na lekcji 12.12 -- Powrót przez referencję i powrót przez adres.
W przypadku wartości typu klasy powiązanie referencji z elementem wydłuży czas życia całego obiektu.
Dlaczego więc C++ pozwala na powiązanie stałej referencji z wartością? Odpowiemy na to pytanie na następnej lekcji!
Constexpr lvalue references Opcjonalne
Po zastosowaniu do odniesienia constexpr pozwala na użycie tego odniesienia w wyrażeniu stałym. Odniesienia Constexpr mają szczególne ograniczenie: można je powiązać tylko z obiektami o statycznym czasie trwania (albo globalnymi, albo statycznymi lokalnymi). Dzieje się tak, ponieważ kompilator wie, gdzie w pamięci zostaną utworzone instancje obiektów statycznych, więc może traktować ten adres jako stałą czasową kompilacji.
Odwołanie constexpr nie może wiązać się z (niestatyczną) zmienną lokalną. Dzieje się tak, ponieważ adres zmiennych lokalnych nie jest znany, dopóki nie zostanie faktycznie wywołana funkcja, w której są zdefiniowane.
int g_x { 5 };
int main()
{
[[maybe_unused]] constexpr int& ref1 { g_x }; // ok, can bind to global
static int s_x { 6 };
[[maybe_unused]] constexpr int& ref2 { s_x }; // ok, can bind to static local
int x { 6 };
[[maybe_unused]] constexpr int& ref3 { x }; // compile error: can't bind to non-static object
return 0;
}Definiując odwołanie constexpr do zmiennej const, musimy zastosować zarówno constexpr (co dotyczy odniesienia), jak i const (co dotyczy typu, do którego się odwołujemy).
int main()
{
static const int s_x { 6 }; // a const int
[[maybe_unused]] constexpr const int& ref2 { s_x }; // needs both constexpr and const
return 0;
}Biorąc pod uwagę te ograniczenia, odniesienia constexpr zazwyczaj nie widzą zbyt wiele użyj.

