Udostępnij za pośrednictwem


Deklarator odwołań Rvalue: &&

Przechowuje odwołanie do wyrażenia rvalue.

Składnia

rvalue-reference-type-id:
type-specifier-seq&&attribute-specifier-seqopt opt optptr-abstract-declarator

Uwagi

Odwołania Rvalue umożliwiają odróżnienie wartości lvalue od wartości rvalue. Odwołania Lvalue i odwołania do wartości rvalue są składniowo i semantycznie podobne, ale są one zgodne z nieco innymi regułami. Aby uzyskać więcej informacji na temat wartości lvalue i rvalues, zobacz Lvalues and Rvalues (Wartości Lvalue i Rvalues). Aby uzyskać więcej informacji na temat odwołań lvalue, zobacz Lvalue Reference Deklarator: &.

W poniższych sekcjach opisano, jak odwołania rvalue obsługują implementację semantyki przenoszenia i doskonałe przekazywanie.

Przenoszenie semantyki

Odwołania do wartości R obsługują implementację semantyki przenoszenia, co może znacznie zwiększyć wydajność aplikacji. Semantyka przenoszenia umożliwia pisanie kodu, który transferuje zasoby (na przykład dynamicznie przydzielaną pamięć) z jednego obiektu do innego. Przenoszenie semantyki działa, ponieważ umożliwia transfer zasobów z obiektów tymczasowych: tych, do których nie można odwoływać się w innym miejscu w programie.

Aby zaimplementować semantyka przenoszenia, zazwyczaj należy podać konstruktor przenoszenia i opcjonalnie operator przypisania przenoszenia (operator=) do klasy. Operacje kopiowania i przypisywania, których źródła są wartościami rvalue, a następnie automatycznie korzystają z semantyki przenoszenia. W przeciwieństwie do domyślnego konstruktora kopiowania kompilator nie udostępnia domyślnego konstruktora przenoszenia. Aby uzyskać więcej informacji na temat pisania i używania konstruktora przenoszenia, zobacz Move constructors and move assignment operators (Przenoszenie konstruktorów i operatorów przypisania przenoszenia).

Można również przeciążyć zwykłe funkcje i operatory, aby korzystać z semantyki przenoszenia. Program Visual Studio 2010 wprowadza semantyka przenoszenia do standardowej biblioteki języka C++. Na przykład string klasa implementuje operacje korzystające z semantyki przenoszenia. Rozważmy następujący przykład, który łączy kilka ciągów i wyświetla 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 programem Visual Studio 2010 każde wywołanie przydziela operator+ i zwraca nowy obiekt tymczasowy string (rvalue). operator+ nie może dołączyć jednego ciągu do drugiego, ponieważ nie wie, czy ciągi źródłowe są lvalues lub rvalues. Jeśli ciągi źródłowe są lvalue, mogą być przywoływane w innym miejscu w programie i nie mogą być modyfikowane. Można zmodyfikować operator+ tak, aby przyjmować wartości rvalue przy użyciu odwołań rvalue, do których nie można odwoływać się w innym miejscu w programie. Dzięki tej zmianie operator+ można teraz dołączyć jeden ciąg do innego. Zmiana znacznie zmniejsza liczbę alokacji pamięci dynamicznej, którą string musi wprowadzić klasa. Aby uzyskać więcej informacji na temat string klasy, zobacz basic_string Klasa.

Semantyka przenoszenia pomaga również wtedy, gdy kompilator nie może użyć optymalizacji wartości zwracanej (RVO) ani optymalizacji nazwanych wartości zwracanych (NRVO). W takich przypadkach kompilator wywołuje konstruktor przenoszenia, jeśli typ go definiuje.

Aby lepiej zrozumieć semantyka przenoszenia, rozważmy przykład wstawiania elementu do vector obiektu. Jeśli pojemność vector obiektu zostanie przekroczona, vector obiekt musi ponownie przydzielić wystarczającą ilość pamięci dla jego elementów, a następnie skopiować każdy element do innej lokalizacji pamięci, aby umieścić miejsce dla wstawionego elementu. Gdy operacja wstawiania kopiuje element, najpierw tworzy nowy element. Następnie wywołuje konstruktor kopiujący, aby skopiować dane z poprzedniego elementu do nowego elementu. Na koniec niszczy poprzedni element. Semantyka przenoszenia umożliwia bezpośrednie przenoszenie obiektów bez konieczności podejmowania kosztownych operacji alokacji pamięci i kopiowania.

Aby skorzystać z semantyki przenoszenia w tym przykładzie vector , możesz napisać konstruktor przenoszenia, aby przenieść dane z jednego obiektu do innego.

Aby uzyskać więcej informacji na temat wprowadzania semantyki przenoszenia do standardowej biblioteki języka C++ w programie Visual Studio 2010, zobacz Standardowa biblioteka języka C++.

Doskonałe przekazywanie

Doskonałe przekazywanie zmniejsza zapotrzebowanie na przeciążone funkcje i pomaga uniknąć problemu z przekazywaniem. Problem z przekazywaniem może wystąpić podczas pisania funkcji ogólnej, która przyjmuje odwołania jako jego parametry. Jeśli przekazuje (lub przekazuje) te parametry do innej funkcji, na przykład jeśli przyjmuje parametr typu const T&, wywołana funkcja nie może zmodyfikować wartości tego parametru. Jeśli funkcja ogólna przyjmuje parametr typu T&, nie można wywołać funkcji przy użyciu wartości rvalue (takiej jak obiekt tymczasowy lub literał liczby całkowitej).

Zazwyczaj, aby rozwiązać ten problem, należy podać przeciążone wersje funkcji ogólnej, które przyjmują zarówno T& , jak i const T& dla każdego z jego parametrów. W rezultacie liczba przeciążonych funkcji zwiększa się wykładniczo wraz z liczbą parametrów. Odwołania Rvalue umożliwiają pisanie jednej wersji funkcji, która akceptuje dowolne argumenty. Następnie ta funkcja może przekazać je do innej funkcji tak, jakby inna funkcja została wywołana bezpośrednio.

Rozważmy następujący przykład, który deklaruje cztery typy, W, X, Yi Z. Konstruktor dla każdego typu przyjmuje inną kombinację odwołań innychconst niż const lvalue 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. W poniższym przykładzie pokazano jeden ze sposobów na napisanie tej funkcji:

template <typename T, typename A1, typename A2>
T* factory(A1& a1, A2& a2)
{
   return new T(a1, a2);
}

W poniższym przykładzie pokazano prawidłowe wywołanie factory funkcji:

int a = 4, b = 5;
W* pw = factory<W>(a, b);

Poniższy przykład nie zawiera jednak prawidłowego factory wywołania funkcji. Jest to spowodowane factory tym, że przyjmuje odwołania lvalue, które można modyfikować jako jego parametry, ale jest wywoływane przy użyciu wartości rvalue:

Z* pz = factory<Z>(2, 2);

Zazwyczaj, aby rozwiązać ten problem, należy utworzyć przeciążoną wersję factory funkcji dla każdej kombinacji parametrów A& i const A& . Odwołania Rvalue umożliwiają napisanie jednej wersji factory funkcji, jak pokazano w poniższym 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));
}

W tym przykładzie użyto odwołań rvalue jako parametrów factory funkcji. std::forward Celem funkcji jest przekazanie parametrów funkcji fabryki do konstruktora klasy szablonu.

W poniższym przykładzie main przedstawiono funkcję, która używa poprawionej factory funkcji do tworzenia wystąpień Wklas , X, Yi Z . Poprawiona factory funkcja przekazuje parametry (lvalues lub rvalues) do odpowiedniego konstruktora 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;
}

Właściwości odwołań rvalue

Funkcję można przeciążyć, aby pobrać odwołanie lvalue i odwołanie rvalue.

Przeciążając funkcję, aby pobrać const odwołanie lvalue lub odwołanie rvalue, można napisać kod, który rozróżnia między obiektami niemodyfikowalnymi (lvalues) i modyfikowalnymi wartościami tymczasowymi (rvalues). Obiekt można przekazać do funkcji, która przyjmuje odwołanie rvalue, chyba że obiekt jest oznaczony jako const. W poniższym przykładzie przedstawiono funkcję f, która jest przeciążona, aby pobrać odwołanie lvalue i odwołanie rvalue. Funkcja main wywołuje zarówno f wartości lvalue, jak 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 can't 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 can't modify the parameter.
In f(MemoryBlock&&). This version can modify the parameter.

W tym przykładzie pierwsze wywołanie metody f przekazuje zmienną lokalną (lvalue) jako argument. Drugie wywołanie metody f przekazuje obiekt tymczasowy jako argument. Ponieważ nie można odwołać się do obiektu tymczasowego w innym miejscu w programie, wywołanie wiąże się z przeciążoną wersją f , która przyjmuje odwołanie rvalue, które jest wolne do modyfikowania obiektu.

Kompilator traktuje nazwane odwołanie rvalue jako lvalue i nienazwane odwołanie rvalue jako rvalue.

Funkcje, które przyjmują odwołanie rvalue jako parametr traktują parametr jako lvalue w treści funkcji. Kompilator traktuje nazwane odwołanie rvalue jako lvalue. Jest to spowodowane tym, że nazwany obiekt może być przywołyny przez kilka części programu. Niebezpieczne jest umożliwienie wielu części programu modyfikowania lub usuwania zasobów z tego obiektu. Jeśli na przykład wiele części programu spróbuje przenieść zasoby z tego samego obiektu, tylko pierwszy transfer zakończy się pomyślnie.

W poniższym przykładzie przedstawiono funkcję g, która jest przeciążona, aby pobrać odwołanie lvalue i odwołanie rvalue. Funkcja f przyjmuje odwołanie rvalue jako parametr (nazwane odwołanie rvalue) i zwraca odwołanie rvalue (nienazwane odwołanie rvalue). W wywołaniu metody g z fmetody funkcja rozpoznawania przeciążenia wybiera wersję g , która przyjmuje odwołanie lvalue, ponieważ treść parametru f jest traktowana jako lvalue. W wywołaniu metody g z mainmetody funkcja rozpoznawania przeciążenia wybiera wersję g , która przyjmuje odwołanie rvalue, ponieważ 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 move(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 main funkcja przekazuje wartość rvalue do f. Treść parametru f traktuje jego nazwany parametr jako lvalue. Wywołanie z f elementu w celu g wiąże parametr z odwołaniem lvalue (pierwsza przeciążona wersja elementu g).

  • Można rzutować wartość lvalue do odwołania rvalue.

Funkcja standardowej biblioteki std::move języka C++ umożliwia konwertowanie obiektu na odwołanie rvalue do tego obiektu. Możesz również użyć słowa kluczowego static_cast , aby rzutować lvalue na odwołanie rvalue, jak pokazano w poniższym 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 dedumentują typy argumentów szablonu, a następnie używają reguł zwijania odwołań.

Szablon funkcji przekazujący (lub przekazujący) parametry do innej funkcji jest typowym wzorcem. Ważne jest, aby zrozumieć, jak działa potrącenie typu szablonu dla szablonów funkcji, które przyjmują odwołania rvalue.

Jeśli argument funkcji jest rvalue, kompilator deduuje argument jako odwołanie rvalue. Załóżmy na przykład, że przekazujesz odwołanie rvalue do obiektu typu X do szablonu funkcji, który przyjmuje typ T&& jako jego parametr. Argument szablonu deducelij T ma wartość X, więc parametr ma typ X&&. Jeśli argument funkcji jest lvalue lub const lvalue, kompilator deduuje jego typ jako odwołanie lvalue lub const odwołanie lvalue tego typu.

Poniższy przykład deklaruje jeden szablon struktury, a następnie specjalizuje się w różnych typach referencyjnych. Funkcja print_type_and_value przyjmuje odwołanie rvalue jako parametr i przekazuje go do odpowiedniej wyspecjalizowanej S::print wersji metody. Funkcja main demonstruje różne sposoby wywoływania S::print metody.

// 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ć print_type_and_value każde wywołanie funkcji, kompilator najpierw odlicza argument szablonu. Kompilator stosuje następnie reguły zwijania odwołań, gdy zamienia typy parametrów na argumenty szablonu. Na przykład przekazanie zmiennej s1 lokalnej do print_type_and_value funkcji powoduje, że kompilator tworzy następujący podpis funkcji:

print_type_and_value<string&>(string& && t)

Kompilator używa reguł zwijania odwołań w celu zmniejszenia podpisu:

print_type_and_value<string&>(string& t)

Ta wersja print_type_and_value funkcji przekazuje następnie jej parametr do poprawnej wyspecjalizowanej S::print wersji metody.

Poniższa tabela zawiera podsumowanie reguł zwijania odwołań dla potrącenia typu argumentu szablonu:

Typ rozwinięty Zwinięty typ
T& & T&
T& && T&
T&& & T&
T&& && T&&

Odliczenie argumentu szablonu jest ważnym elementem implementowania doskonałego przekazywania. Sekcja Perfect forwarding zawiera bardziej szczegółowe opisy doskonałego przekazywania dalej.

Podsumowanie

Odwołania do wartości Rvalue odróżniają lvalues od rvalues. Aby zwiększyć wydajność aplikacji, mogą one wyeliminować potrzebę niepotrzebnych alokacji pamięci i operacji kopiowania. Umożliwiają one również pisanie funkcji, która akceptuje dowolne argumenty. Ta funkcja może przekazać je do innej funkcji tak, jakby inna funkcja została wywołana bezpośrednio.

Zobacz też

Wyrażenia z operatorami jednoargumentowymi
Deklarator odwołań Lvalue: &
Wartości Lvalue i rvalues
Przenoszenie konstruktorów i operatorów przypisania przenoszenia (C++)
Standardowa biblioteka C++