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 }; // l-value reference initialized with l-value x
int&& rref{ 5 }; // r-value reference initialized with r-value 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 } }; // 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 here

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

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