W rozdziale 12 wprowadziliśmy koncepcję kategorii wartości (12.2 — Kategorie wartości (lwartości i rwartości)), która jest właściwością wyrażeń, która pomaga określić, czy wyrażenie jest rozwiązywane do wartości, funkcji lub obiektu. Wprowadziliśmy także wartości l i r, abyśmy mogli omówić odniesienia do wartości l.
Jeśli nie masz pewności co do wartości l i r, teraz jest dobry moment, aby odświeżyć ten temat, ponieważ będziemy dużo o nich mówić w tym rozdziale.
Podsumowanie odwołań do wartości L
Przed C++11 w C++ istniał tylko jeden typ referencji, i tak nazywano to po prostu „odniesieniem”. Jednak w C++ 11 nazywa się to referencją do wartości l. Odniesienia do wartości L można inicjować wyłącznie modyfikowalnymi wartościami l.
| Odniesienia do wartości L | Można zainicjować za pomocą | Można modyfikować |
|---|---|---|
| Modyfikowalne l-wartości | Tak | Tak |
| Niemodyfikowalne l-wartości | Nie | Nie |
| R-wartości | Nie | Nie |
Wartości L do stałych obiekty można inicjować zarówno modyfikowalnymi, jak i niemodyfikowalnymi wartościami l i r. Jednakże tych wartości nie można modyfikować.
| Odniesienie wartości L do const | Można zainicjować za pomocą | Można modyfikować |
|---|---|---|
| Modyfikowalne l-wartości | Tak | Nie |
| Niemodyfikowalne l-wartości | Tak | Nie |
| R-wartości | Tak | Nie |
Odniesienia wartości L do obiektów const są szczególnie przydatne, ponieważ pozwalają nam przekazać dowolny typ argumentu (wartość l lub wartość r) do funkcji bez tworzenia kopii argumentu.
Wartość R referencje
C++ 11 dodaje nowy typ odniesienia zwany odwołaniem do wartości r. Odniesienie do wartości r to odniesienie zaprojektowane tak, aby było inicjowane wartością r (tylko). Podczas gdy odniesienie do wartości l jest tworzone przy użyciu pojedynczego znaku ampersand, odniesienie do wartości r jest tworzone przy użyciu podwójnego znaku ampersand:
int x{ 5 };
int& lref{ x }; // l-value reference initialized with l-value x
int&& rref{ 5 }; // r-value reference initialized with r-value 5Odniesienia do wartości R nie mogą być inicjowane za pomocą l-wartości.
| Odniesienie do wartości R | Można zainicjować za pomocą | Można modyfikować |
|---|---|---|
| Modyfikowalne l-wartości | Nie | Nie |
| Niemodyfikowalne l-wartości | Nie | Nie |
| R-wartości | Tak | Tak |
| R-wartość odniesienie do const | Można zainicjować za pomocą | Można modyfikować |
|---|---|---|
| Modyfikowalne l-wartości | Nie | Nie |
| Niemodyfikowalne l-wartości | Nie | Nie |
| R-wartości | Tak | Nie |
R-wartości referencyjne mają dwie przydatne właściwości. Po pierwsze, odniesienia do wartości r wydłużają czas życia obiektu, którym są inicjowane, do czasu życia odniesienia do wartości r (mogą to zrobić również odniesienia do wartości r do obiektów const). Po drugie, odniesienia do wartości r niebędące stałymi pozwalają modyfikować wartość r!
Przyjrzyjmy się kilku przykładom:
#include <iostream>
class Fraction
{
private:
int m_numerator { 0 };
int m_denominator { 1 };
public:
Fraction(int numerator = 0, int denominator = 1) :
m_numerator{ numerator }, m_denominator{ denominator }
{
}
friend std::ostream& operator<<(std::ostream& out, const Fraction& f1)
{
out << f1.m_numerator << '/' << f1.m_denominator;
return out;
}
};
int main()
{
auto&& rref{ Fraction{ 3, 5 } }; // r-value reference to temporary Fraction
// f1 of operator<< binds to the temporary, no copies are created.
std::cout << rref << '\n';
return 0;
} // rref (and the temporary Fraction) goes out of scope hereTen program wypisuje:
3/5
Jako obiekt anonimowy, Fraction(3, 5) normalnie wykraczałoby poza zakres na końcu wyrażenia, w którym jest zdefiniowane. Ponieważ jednak inicjujemy za jego pomocą odniesienie do wartości r, jego czas trwania wydłuża się aż do końca bloku. Możemy następnie użyć tego odniesienia do wartości r do wydrukowania wartości Ułamka.
Przyjrzyjmy się teraz mniej intuicyjnemu przykładowi:
#include <iostream>
int main()
{
int&& rref{ 5 }; // because we're initializing an r-value reference with a literal, a temporary with value 5 is created here
rref = 10;
std::cout << rref << '\n';
return 0;
}Ten program wypisuje:
10
Chociaż może wydawać się dziwne zainicjowanie odniesienia do wartości r wartością literałową, a następnie możliwość zmiany tej wartości, podczas inicjowania odniesienia do wartości r za pomocą literału obiekt tymczasowy jest konstruowany z literału, tak że odwołanie odnosi się do obiektu tymczasowego, a nie dosłownego wartość.
Odniesienia do wartości R nie są zbyt często używane w żaden z przedstawionych powyżej sposobów.
Odniesienia do wartości R jako parametry funkcji
Odniesienia do wartości R są częściej używane jako parametry funkcji. Jest to najbardziej przydatne w przypadku przeciążeń funkcji, gdy chcesz mieć inne zachowanie argumentów o wartości l i wartości r.
#include <iostream>
void fun(const int& lref) // l-value arguments will select this function
{
std::cout << "l-value reference to const: " << lref << '\n';
}
void fun(int&& rref) // r-value arguments will select this function
{
std::cout << "r-value reference: " << rref << '\n';
}
int main()
{
int x{ 5 };
fun(x); // l-value argument calls l-value version of function
fun(5); // r-value argument calls r-value version of function
return 0;
}Wypisuje:
l-value reference to const: 5 r-value reference: 5
Jak widać, po przekazaniu wartości l, przeciążona funkcja została przekształcona w wersję z odwołaniem do wartości l. Po przekazaniu wartości r przeciążona funkcja została przekształcona w wersję zawierającą odwołanie do wartości r (uważa się to za lepsze dopasowanie niż odwołanie do wartości l do stałej).
Dlaczego miałbyś kiedykolwiek chcieć to zrobić? Omówimy to bardziej szczegółowo w następnej lekcji. Nie trzeba dodawać, że jest to ważna część semantyki ruchu.
Zmienne referencyjne Rvalue to lvalues
Rozważ następujący fragment:
int&& ref{ 5 };
fun(ref);Której wersji fun spodziewałbyś się, że wywoła to powyższe: fun(const int&) lub fun(int&&)?
Odpowiedź może Cię zaskoczyć. Wywołuje to fun(const int&).
Chociaż zmienna ref ma typ int&&, użyta w wyrażeniu jest wartością (jak wszystkie nazwane zmienne). Typ obiektu i jego kategoria wartości są niezależne.
Wiesz już, że literał 5 jest wartością typu int, I int x jest wartością typu int. Podobnie int&& ref jest wartością typu int&&.
Więc nie tylko fun(ref) wywołuje fun(const int&), ale nawet nie pasuje fun(int&&), ponieważ odniesienia do wartości nie mogą być powiązane z lwartościami.
Zwracanie odniesienia do wartości r
Prawie nigdy nie powinieneś zwracać odniesienia do wartości r, z tego samego powodu prawie nigdy nie powinieneś zwracać odniesienia do wartości l. W większości przypadków zwrócisz wiszące odwołanie, gdy na końcu funkcji odnośny obiekt wyjdzie poza zakres.
Czas quizu
Pytanie nr 1
Określ, które z poniższych instrukcji opatrzonych literami nie zostaną skompilowane:
int main()
{
int x{};
// l-value references
int& ref1{ x }; // A
int& ref2{ 5 }; // B
const int& ref3{ x }; // C
const int& ref4{ 5 }; // D
// r-value references
int&& ref5{ x }; // E
int&& ref6{ 5 }; // F
const int&& ref7{ x }; // G
const int&& ref8{ 5 }; // H
return 0;
}
