Deklarator odwołania do wartości R: &&
Zawiera odwołanie do wyrażenia rvalue.
type-id && cast-expression
Uwagi
Odwołania Rvalue umożliwiają odróżnienie wartości lvalue od rvalue.Odwołania lvalue i odwołania rvalue są składniowo i semantycznie podobne, ale mają nieco inne reguły.Aby uzyskać więcej informacji dotyczących l- i r-wartości, zobacz Lvalues i Rvalues.Aby uzyskać więcej informacji na temat odwołań do l-wartości, zobacz Deklarator odwołania do wartości L: &.
W poniższych sekcjach opisano, jak odwołania rvalue obsługują wdrażanie semantyki przenoszenia i przekazywania.
Semantyka przenoszenia
Odwołania Rvalue wspierają implementację semantyki przenoszenia, co może znacząco zwiększyć wydajność aplikacji.Semantyka przenoszenia umożliwia napisanie kodu, który przenosi zasoby (takie jak pamięć przydzielana dynamicznie) z jednego obiektu do drugiego.Semantyka przenoszenia działa, ponieważ umożliwia przenoszenie zasobów z tymczasowych obiektów, do których nie można odwoływać się z innych miejsc w programie.
Aby zaimplementować semantykę przenoszenia, zazwyczaj podajesz konstruktor przenoszący, a opcjonalnie operator przypisania przenoszenia (operator=), do swojej klasy.Kopiuj i przypisz operacje, których źródłami są r-wartości, a następnie automatycznie skorzystaj z semantyki przeniesienia.W przeciwieństwie do domyślnego konstruktora kopii, kompilator nie udostępnia domyślnego konstruktora przenoszącego.Aby uzyskać więcej informacji dotyczących sposobu pisania konstruktora przenoszącego i sposobu używania go w aplikacji, zobacz Porady: zapisywanie konstruktora przenoszenia.
Możesz także przeciążać zwykłe funkcje i operatory, aby skorzystać z semantyki przenoszenia.Visual C++ 2010 wprowadza semantykę ruchu do standardowych bibliotek szablonów (STL).Na przykład klasa string implementuje operacje wykonujące semantykę przenoszenia.Rozważmy następujący przykład, który łączy kilka ciągów i wypisuje wynik:
// string_concatenation.cpp
// compile with: /EHsc
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s = string("h") + "e" + "ll" + "o";
cout << s << endl;
}
Przed Visual C++ 2010 każde wywołanie operator+ przydzielało i zwracało nowy tymczasowy obiekt string (r-wartość).Obiekt operator+ nie może dołączyć jednego ciągu do drugiego, ponieważ nie wie, czy ciągi źródłowe są wartościami lvalue czy rvalue.Jeśli oba ciągi źródłowe są wartościami lvalue, mogą istnieć do nich odwołania z innych miejsc w programie i dlatego nie mogą zostać zmodyfikowane.Za pomocą odwołań r-wartości, operator+ może zostać zmodyfikowany do r-wartości, do której nie można odwoływać się gdzie indziej w programie.W związku z tym operator+ mogą teraz łączyć jeden ciąg z innym.Może to znacznie zmniejszyć liczbę alokacji pamięci dynamicznej, które musi wykonać klasa string.Aby uzyskać więcej informacji dotyczących klasy string, wejdź na basic_string — Klasa.
Semantyka przenoszenia pomaga również, gdy kompilator nie można używać optymalizacji wartość zwracanych (RVO) ani optymalizacji nazwanych wartość zwracanych (NRVO).W takich przypadkach kompilator wywołuje konstruktora przenoszącego, jeśli typ określa go.Aby uzyskać więcej informacji o optymalizacji nazwanej zwracanej wartości, zobacz Optymalizacja nazwanej zwracanej wartości w programie Visual C++ 2005.
Aby lepiej zrozumieć semantykę przenoszenia, rozważ przykład wstawiania elementu do obiektu vector.Jeśli pojemność obiektu vector zostanie przekroczona, obiekt vector musi zmienić przydział pamięci dla swoich elementów, a następnie skopiować każdy element do innej lokalizacji w pamięci, aby zwolnić miejsce dla wstawionego elementu.Gdy operacja wstawiania kopiuje element, tworzy nowy element, wywołuje konstruktor Kopiuj, aby kopiować dane z poprzedniego elementu do nowego elementu, a następnie niszczy poprzedni element.Semantyka przenoszenia umożliwia przenoszenie obiektów bezpośrednio, bez konieczności wykonywania kosztownych operacji alokacji pamięci i kopiowania.
Aby skorzystać z semantyki przenoszenia w przykładzie vector, można napisać konstruktor przenoszący, aby przenieść dane z jednego obiektu do drugiego.
Aby uzyskać więcej informacji na temat wprowadzenia semantyki przenoszenia do biblioteki STL w programie Visual C++ 2010, zobacz Odwołanie do standardowej biblioteki C++.
Perfekcyjne przekazywanie
Perfekcyjne przekazywanie ogranicza potrzebę przeciążania funkcji i pomaga uniknąć problemów z przesyłaniem dalej.Problem z przesyłaniem może wystąpić, gdy zapisywana jest funkcja ogólna, która przyjmuje odniesienia parametrów i przekazuje (lub przesyła dalej) te parametry do innej funkcji.Na przykład jeśli ogólna funkcja przyjmuje parametr typu const T&, wywoływana funkcja nie może modyfikować wartości tego parametru.Jeśli funkcja ogólna przyjmuje parametr typu T&, nie może zostać wywołana przy użyciu wartości rvalue (takie jak tymczasowy obiekt lub literał liczby całkowitej).
Zwykle aby rozwiązać ten problem, należy podać przeciążone wersje funkcji ogólnej, które przyjmują typy T& i const T& dla każdego z jej parametrów.W efekcie wiele funkcji zastąpionej wykładniczo zwiększa się liczba parametrów.Odwołania Rvalue umożliwiają pisanie jednej wersji funkcji, która przyjmuje argumenty dowolne i przekazuje je do innej funkcji, tak jakby została ona bezpośrednio wywołana.
Rozważmy następujący przykład, w którym zadeklarowano cztery typy W, X, Y i Z.Konstruktor dla każdego typu ma inną kombinację const i non-const wartość lvalue jest wspominana jako jego parametry.
struct W
{
W(int&, int&) {}
};
struct X
{
X(const int&, int&) {}
};
struct Y
{
Y(int&, const int&) {}
};
struct Z
{
Z(const int&, const int&) {}
};
Załóżmy, że chcesz napisać funkcję ogólną, która generuje obiekty.Poniższy przykład ukazuje, jak można zapisać tę funkcję:
template <typename T, typename A1, typename A2>
T* factory(A1& a1, A2& a2)
{
return new T(a1, a2);
}
Poniższy przykład ilustruje poprawne wywoływanie funkcji factory:
int a = 4, b = 5;
W* pw = factory<W>(a, b);
Natomiast poniższy przykład nie zawiera prawidłowego wywołania funkcji factory, ponieważ funkcja factory ma odwołania lvalue, które można modyfikować jako jej parametry, ale jest wywoływana za pomocą wartości rvalue:
Z* pz = factory<Z>(2, 2);
Zwykle aby rozwiązać ten problem, należy utworzyć przeciążoną wersję funkcji factory dla każdej kombinacji parametrów A& i const A&.Odwołania Rvalue umożliwiają pisanie jednej wersji funkcji factory, jak pokazano w następującym przykładzie:
template <typename T, typename A1, typename A2>
T* factory(A1&& a1, A2&& a2)
{
return new T(std::forward<A1>(a1), std::forward<A2>(a2));
}
Ten przykład używa odwołań rvalue jako parametrów dla funkcji factory.Celem funkcji std::forwardjest przekazywanie parametrów funkcji fabryki do konstruktora klasy szablonu.
Poniższy przykład ukazuje funkcję main, która używa zweryfikowanej funkcji factory do tworzenia wystąpień klas W, X, Y, i Z.Zweryfikowana funkcja factory przekazuje parametry (lvalues lub rvalues) do konstruktora właściwej klasy.
int main()
{
int a = 4, b = 5;
W* pw = factory<W>(a, b);
X* px = factory<X>(2, b);
Y* py = factory<Y>(a, 2);
Z* pz = factory<Z>(2, 2);
delete pw;
delete px;
delete py;
delete pz;
}
Dodatkowe właściwości odwołań r-wartości
Możesz doprowadzić do przeciążenia funkcji aby uzyskać odwołanie lvalue i odwołanie rvalue.
Przy przeciążeniu funkcji w celu uzyskania odwołania l-wartości const lub r-wartości, można napisać kod, który rozróżnia obiekty niemodyfikowalne (l-wartości) i wartości modyfikowalne tymczasowo (r-wartości).Możesz przekazać obiekt do funkcji, która ma odwołanie rvalue, o ile obiekt nie jest oznaczony jako const.Poniższy przykład ukazuje funkcję f, która jest przeciążona, aby pobrać odwołanie lvalue i rvalue.Funkcja main wywołuje f z lvalues i rvalue.
// reference-overload.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;
// A class that contains a memory resource.
class MemoryBlock
{
// TODO: Add resources for the class here.
};
void f(const MemoryBlock&)
{
cout << "In f(const MemoryBlock&). This version cannot modify the parameter." << endl;
}
void f(MemoryBlock&&)
{
cout << "In f(MemoryBlock&&). This version can modify the parameter." << endl;
}
int main()
{
MemoryBlock block;
f(block);
f(MemoryBlock());
}
Ten przykład generuje następujące wyniki:
In f(const MemoryBlock&). This version cannot modify the parameter.
In f(MemoryBlock&&). This version can modify the parameter.
W tym przykładzie pierwsze wywołanie do obiektu f przekazuje zmienną lokalną (lvalue) jako argument.Drugie wywołanie do f przekazuje tymczasowy obiekt jako argument.Ponieważ do obiektów tymczasowych nie można odwoływać się gdzie indziej w programie, wywołanie jest powiązane z przeciążoną wersją f pobierającą odniesienia r-wartości, która może modyfikować dany obiekt.
Kompilator traktuje odwołanie nazwane rvalue jako lvalue i odwołanie nienazwane rvalue jako rvalue.
Podczas pisania funkcji, która przyjmuje odwołanie rvalue za parametr, ten parametr jest traktowany jako lvalue w treści funkcji.Kompilator traktuje odwołanie nazwane rvalue jako lvalue, ponieważ nazwany obiekt może być wspominany przez kilka części programu. Byłoby niebezpieczne zezwolić wielu częściom programu na modyfikowanie lub usuwanie zasobów z tego obiektu.Na przykład jeśli wiele części programu próbuje przenieść zasoby z tego samego obiektu, tylko pierwsza część pomyślnie spowoduje przeniesienie zasobu.
Poniższy przykład ukazuje funkcję g, która jest przeciążona, aby pobrać odwołanie l-wartości i r-wartości.Funkcja f przyjmuje odwołanie rvalue za swój parametr (nazwane odwołanie rvalue) i zwraca odwołanie rvalue (nienazwane odwołanie rvalue).W wywołaniu do obiektu g z obiektu f funkcja rozpoznawania przeciążeń wybiera wersję obiektu g, która pobiera odwołanie lvalue, ponieważ treść obiektu f traktuje jego parametr jako lvalue.W wywołaniu do obiektu g z obiektu main funkcja rozpoznawania przeciążeń wybiera wersję obiektu g, która pobiera odwołanie rvalue, ponieważ obiekt f zwraca odwołanie rvalue.
// named-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;
// A class that contains a memory resource.
class MemoryBlock
{
// TODO: Add resources for the class here.
};
void g(const MemoryBlock&)
{
cout << "In g(const MemoryBlock&)." << endl;
}
void g(MemoryBlock&&)
{
cout << "In g(MemoryBlock&&)." << endl;
}
MemoryBlock&& f(MemoryBlock&& block)
{
g(block);
return block;
}
int main()
{
g(f(MemoryBlock()));
}
Ten przykład generuje następujące wyniki:
In g(const MemoryBlock&).
In g(MemoryBlock&&).
W tym przykładzie funkcja main przekazuje wartości rvalue do obiektu f.Treść f traktuje jego nazwany parametr jako lvalue.Wywołanie z f do g powiązuje parametr z odwołaniem lvalue (pierwsza przeciążona wersja g).
- Możesz rzutować lvalue na odwołanie rvalue.
Funkcja STL std::move umożliwia konwertowanie obiektu do odwołania rvalue do tego obiektu.Alternatywnie można użyć słowa kluczowego static_cast, aby przenieść odwołanie l-wartości do r-wartości, jak pokazano w następującym przykładzie:
// cast-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;
// A class that contains a memory resource.
class MemoryBlock
{
// TODO: Add resources for the class here.
};
void g(const MemoryBlock&)
{
cout << "In g(const MemoryBlock&)." << endl;
}
void g(MemoryBlock&&)
{
cout << "In g(MemoryBlock&&)." << endl;
}
int main()
{
MemoryBlock block;
g(block);
g(static_cast<MemoryBlock&&>(block));
}
Ten przykład generuje następujące wyniki:
In g(const MemoryBlock&).
In g(MemoryBlock&&).
Szablony funkcji wywnioskowują swoje typy argumentów szablonu, a następnie używają reguł zwijanie odwołań.
To powszechne zapisać szablon funkcji, który przekazuje (lub przesyła dalej) jej parametry do innej funkcji.Jest ważne, aby zrozumieć, jak działa dedukcja typu szablonu dla szablonów funkcji, które przyjmują odwołania do wartości rvalue.
Jeżeli argumentem funkcji jest wartość rvalue, kompilator wywnioskowuje, że argument jest odwołaniem rvalue.Na przykład w przypadku przekazania odwołania rvalue do obiektu typu X do funkcji szablonu, która przyjmuje typ T&& jako parametr, funkcja wnioskowania argumentu szablonu wnioskuje, że typ T jest typem X.W związku z tym, parametr ma typ X&&.Jeśli argument funkcji jest wartością lvalue lub lvalue const, kompilator wywnioskowuje, że jej typ jest odwołaniem lvalue lub odwołaniem lvalue const tego typu.
Poniższy przykład deklaruje jeden szablon struktury a następnie specjalizuje go dla różnych typów odwołań.Funkcja print_type_and_value przyjmuje odwołanie rvalue za parametr i przekazuje go do odpowiedniej wersji specjalistycznej metody S::print.Funkcja main ilustruje różne sposoby wywoływania metody S::print.
// template-type-deduction.cpp
// Compile with: /EHsc
#include <iostream>
#include <string>
using namespace std;
template<typename T> struct S;
// The following structures specialize S by
// lvalue reference (T&), const lvalue reference (const T&),
// rvalue reference (T&&), and const rvalue reference (const T&&).
// Each structure provides a print method that prints the type of
// the structure and its parameter.
template<typename T> struct S<T&> {
static void print(T& t)
{
cout << "print<T&>: " << t << endl;
}
};
template<typename T> struct S<const T&> {
static void print(const T& t)
{
cout << "print<const T&>: " << t << endl;
}
};
template<typename T> struct S<T&&> {
static void print(T&& t)
{
cout << "print<T&&>: " << t << endl;
}
};
template<typename T> struct S<const T&&> {
static void print(const T&& t)
{
cout << "print<const T&&>: " << t << endl;
}
};
// This function forwards its parameter to a specialized
// version of the S type.
template <typename T> void print_type_and_value(T&& t)
{
S<T&&>::print(std::forward<T>(t));
}
// This function returns the constant string "fourth".
const string fourth() { return string("fourth"); }
int main()
{
// The following call resolves to:
// print_type_and_value<string&>(string& && t)
// Which collapses to:
// print_type_and_value<string&>(string& t)
string s1("first");
print_type_and_value(s1);
// The following call resolves to:
// print_type_and_value<const string&>(const string& && t)
// Which collapses to:
// print_type_and_value<const string&>(const string& t)
const string s2("second");
print_type_and_value(s2);
// The following call resolves to:
// print_type_and_value<string&&>(string&& t)
print_type_and_value(string("third"));
// The following call resolves to:
// print_type_and_value<const string&&>(const string&& t)
print_type_and_value(fourth());
}
Ten przykład generuje następujące wyniki:
print<T&>: first
print<const T&>: second
print<T&&>: third
print<const T&&>: fourth
Aby rozwiązać każde wywołanie do funkcji print_type_and_value, kompilator wykonuje najpierw odliczanie argumentu szablon.Następnie kompilator stosuje odniesienie zwijania reguł, gdy zastępuje wywnioskowane argumenty szablonu dla typów parametrów.Na przykład przekazanie zmiennej lokalnej s1 do funkcji print_type_and_value powoduje, że kompilator generuje następujący podpis funkcji:
print_type_and_value<string&>(string& && t)
Kompilator używa odniesienie zwijania reguł do zmniejszania podpisu w następujący sposób:
print_type_and_value<string&>(string& t)
Ta wersja funkcji print_type_and_value następnie przesyła parametr do poprawnej wersji specjalistycznej wersji metody S::print.
Poniższa tabela podsumowuje zwijane zasady odwołania dla wyznaczenia typu argumentu szablonu:
Typ rozszerzony |
Typ zwinięty |
T& & |
T& |
T& && |
T& |
T&& & |
T& |
T&& && |
T&& |
Odliczanie argumentu szablon jest istotnym elementem wdrażania przekazywania.Sekcja Doskonałe przekazywanie dalej, przedstawiona we wcześniejszej części tego tematu, bardziej szczegółowo opisuje perfekcyjne przekazywanie.
Podsumowanie
Odwołania Rvalue odróżniają wartości lvalues od rvalues.Mogą one pomóc zwiększyć wydajność aplikacji poprzez wyeliminowanie potrzeby przydzielania niepotrzebnych alokacji pamięci i operacji kopiowania.Umożliwiają one również pisanie jednej wersji funkcji, która przyjmuje argumenty dowolne i przekazuje je do innej funkcji, tak jakby została ona bezpośrednio wywołana.
Zobacz też
Zadania
Porady: zapisywanie konstruktora przenoszenia
Informacje
Wyrażenia z operatorami jednoargumentowymi
Deklarator odwołania do wartości L: &