22.2 -- Odniesienia do wartości R

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 LMożna zainicjować za pomocąMożna modyfikować
Modyfikowalne l-wartościTakTak
Niemodyfikowalne l-wartościNieNie
R-wartościNieNie

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 constMożna zainicjować za pomocąMożna modyfikować
Modyfikowalne l-wartościTakNie
Niemodyfikowalne l-wartościTakNie
R-wartościTakNie

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 }; // odniesienie do wartości l zainicjowane wartością l x
int&& rref{ 5 }; // odniesienie do wartości r zainicjowane wartością r 5

Odniesienia do wartości R nie mogą być inicjowane za pomocą l-wartości.

Odniesienie do wartości RMożna zainicjować za pomocąMożna modyfikować
Modyfikowalne l-wartościNieNie
Niemodyfikowalne l-wartościNieNie
R-wartościTakTak

R-wartość odniesienie do constMożna zainicjować za pomocąMożna modyfikować
Modyfikowalne l-wartościNieNie
Niemodyfikowalne l-wartościNieNie
R-wartościTakNie

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 } }; // odniesienie do wartości r do tymczasowego ułamka
	
	// f1 operatora<< wiąże się z tymczasowym, nie są tworzone żadne kopie.
	std::cout << rref << '\n';
 
	return 0;
} // rref (i tymczasowa frakcja) wykracza tutaj poza zakres

Ten 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 }; // ponieważ inicjujemy odwołanie do wartości r za pomocą literału, tworzony jest tutaj element tymczasowy o wartości 5
    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) // argumenty o wartości l wybiorą tę funkcję
{
	std::cout << "l-value reference to const: " << lref << '\n';
}

void fun(int&& rref) // argumenty-wartość r wybierają tę funkcję
{
	std::cout << "r-value reference: " << rref << '\n';
}

int main()
{
	int x{ 5 };
	fun(x); // l-wartość wywołuje wersję funkcji o l-wartości
	fun(5); // argument r-wartość wywołuje wersję funkcji r-wartość

	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{};

	// odniesienia do l-wartości
	int& ref1{ x }; // A
	int& ref2{ 5 }; // B

	const int& ref3{ x }; // C
	const int& ref4{ 5 }; // D

	// odniesienia do wartości r
	int&& ref5{ x }; // E
	int&& ref6{ 5 }; // F

	const int&& ref7{ x }; // G
	const int&& ref8{ 5 }; // H
	
	return 0;
}

Pokaż rozwiązanie

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
139 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze