Декларатор ссылки Rvalue: &&

Содержит ссылку на выражение rvalue.

Синтаксис

rvalue-reference-type-id:
type-specifier-seq&&attribute-specifier-seqoptptr-abstract-declaratoropt

Замечания

Ссылки rvalue позволяют различать значения lvalue и rvalue. Ссылки Lvalue и ссылки rvalue синтаксически и семантично похожи, но они следуют немного разными правилами. Дополнительные сведения о lvalues и rvalues см. в разделе Lvalues и Rvalues. Дополнительные сведения о ссылках на lvalue см. в статье Lvalue Reference Declarator: >.

В следующих разделах описывается, как ссылки rvalue поддерживают реализацию семантики перемещения и идеальной пересылки.

Семантика перемещения

Ссылки на Rvalue поддерживают реализацию семантики перемещения, что может значительно повысить производительность приложений. Семантика перемещения позволяет создавать код, который переносит ресурсы (например, динамически выделяемую память) из одного объекта в другой. Семантика перемещения работает, так как она позволяет передавать ресурсы из временных объектов: те, на которые нельзя ссылаться в другом месте программы.

Для реализации семантики перемещения обычно предоставляется конструктор перемещения и при необходимости оператор назначения перемещения (operator=) для класса. Операции копирования и присваивания, источниками которых являются значения rvalue, затем автоматически используют семантику перемещения. В отличие от конструктора копирования по умолчанию компилятор не предоставляет конструктор перемещения по умолчанию. Дополнительные сведения о написании и использовании конструктора перемещения см. в разделе "Перемещение конструкторов" и операторов назначения перемещения.

Можно также перегрузить обычные функции и операторы, чтобы воспользоваться преимуществами семантики перемещения. Visual Studio 2010 представляет семантику перемещения в стандартную библиотеку C++. Например, string класс реализует операции, использующие семантику перемещения. Рассмотрим следующий пример, в котором объединяются несколько строк и выводится результат:

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

Перед Visual Studio 2010 каждый вызов operator+ выделяет и возвращает новый временный string объект (rvalue). operator+ не может добавить одну строку к другой, так как она не знает, являются ли исходные строки lvalues или rvalues. Если исходные строки являются lvalues, они могут ссылаться в другом месте программы и поэтому не должны быть изменены. Вы можете изменить operator+ значение rvalue, используя ссылки rvalue, на которые нельзя ссылаться в другом месте программы. При этом изменении operator+ теперь можно добавить одну строку в другую. Изменение значительно уменьшает количество динамических выделений памяти, которые string должен сделать класс. Дополнительные сведения о классе см. в string разделе basic_string "Класс".

Семантика перемещения также помогает, если компилятор не может использовать оптимизацию возвращаемого значения (RVO) или оптимизацию именованных возвращаемых значений (NRVO). В таких случаях компилятор вызывает конструктор перемещения, если он определен в типе.

Для лучшего понимания семантики перемещения рассмотрим пример вставки элемента в объект vector. Если емкость vector объекта превышается, vector объект должен перераспределить достаточно памяти для его элементов, а затем скопировать каждый элемент в другое расположение памяти, чтобы освободить место для вставленного элемента. Когда операция вставки копирует элемент, он сначала создает новый элемент. Затем он вызывает конструктор копирования, чтобы скопировать данные из предыдущего элемента в новый элемент. Наконец, он уничтожает предыдущий элемент. Семантика перемещения позволяет перемещать объекты непосредственно без необходимости выполнять дорогостоящие операции выделения памяти и копирования.

Чтобы воспользоваться преимуществами семантики перемещения в примере vector, можно создать конструктор перемещения для перемещения данных из одного объекта в другой.

Дополнительные сведения о внедрении семантики перемещения в стандартную библиотеку C++ в Visual Studio 2010 см . в стандартной библиотеке C++.

Идеальное переадресация

Точная пересылка уменьшает необходимость в использовании перегруженных функций и позволяет избежать проблем пересылки. Проблема переадресации может возникать при записи универсальной функции, которая принимает ссылки в качестве параметров. Если он передает (или пересылает) эти параметры другой функции, например, если он принимает параметр типа const T&, то вызываемая функция не может изменить значение этого параметра. Если универсальная функция принимает параметр типа T&, функция не может вызываться с помощью rvalue (например, временного объекта или целочисленного литерала).

Обычно для решения этой проблемы необходимо предоставить перегруженные версии универсальной функции, принимающие для каждого из своих параметров значения T& и const T&. В результате число перегруженных функций экспоненциально возрастает по мере увеличения числа параметров. Ссылки на Rvalue позволяют писать одну версию функции, которая принимает произвольные аргументы. Затем эта функция может перенаправить их в другую функцию, как если бы другая функция была вызвана напрямую.

Рассмотрим следующий пример, в котором определяются четыре типа: W, X, Y и Z. Конструктор для каждого типа принимает разные сочетания const ссылок и неconst lvalue в качестве параметров.

struct W
{
   W(int&, int&) {}
};

struct X
{
   X(const int&, int&) {}
};

struct Y
{
   Y(int&, const int&) {}
};

struct Z
{
   Z(const int&, const int&) {}
};

Предположим, требуется написать универсальную функцию, которая создает объекты. В следующем примере показан один из возможных способов написания этой функции:

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

В следующем примере показан допустимый вызов функции factory:

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

Однако в следующем примере не содержится допустимый вызов factory функции. Это связано с тем, что factory принимает ссылки lvalue, которые изменяются в качестве параметров, но вызывается с помощью rvalues:

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

Обычно для решения этой проблемы необходимо создать перегруженные версии функции factory для каждого сочетания параметров A& и const A&. Ссылки rvalue позволяют создать одну версию функции factory, как показано в следующем примере:

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

В этом примере в качестве параметров функции factory используются ссылки rvalue. Цель std::forward функции — перенаправить параметры функции фабрики конструктору класса шаблона.

В следующем примере показана функция main, использующая измененную функцию factory для создания экземпляров классов W, X, Y и Z. Измененная функция factory пересылает свои параметры (значения lvalue или rvalue) в конструктор соответствующего класса.

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

Свойства ссылок rvalue

Вы можете перегрузить функцию, чтобы получить ссылку lvalue и ссылку rvalue.

Перегрузив функцию для получения const ссылки lvalue или ссылки rvalue, можно написать код, который различает не модификируемые объекты (lvalues) и изменяемые временные значения (rvalues). Объект можно передать в функцию, которая принимает ссылку rvalue, если объект не помечен как const. В следующем примере показана перегруженная функция f, принимающая ссылки lvalue и rvalue. Функция main вызывает функцию f как со значениями lvalue, так и со значениями 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());
}

В примере получается следующий вывод.

In f(const MemoryBlock&). This version can't modify the parameter.
In f(MemoryBlock&&). This version can modify the parameter.

В этом примере при первом вызове функции f в качестве аргумента передается локальная переменная (значение lvalue). При втором вызове функции f в качестве аргумента передается временный объект. Так как временный объект не может ссылаться в другом месте программы, вызов привязывается к перегруженной версии f , которая принимает ссылку rvalue, которая свободна для изменения объекта.

Компилятор обрабатывает именованную ссылку rvalue как lvalue и неназванную ссылку rvalue как rvalue.

Функции, которые принимают ссылку rvalue в качестве параметра, рассматривают параметр как lvalue в тексте функции. Компилятор обрабатывает именованную ссылку rvalue как lvalue. Это связано с тем, что именованный объект может ссылаться на несколько частей программы. Опасно разрешить нескольким частям программы изменять или удалять ресурсы из этого объекта. Например, если несколько частей программы пытаются передать ресурсы из одного объекта, выполняется только первая передача.

В следующем примере показана перегруженная функция g, принимающая ссылки lvalue и rvalue. Функция f принимает ссылку rvalue в качестве своего параметра (именованная ссылка rvalue) и возвращает ссылку rvalue (безымянная ссылка rvalue). При вызове функции g из функции f механизм разрешения перегрузок выбирает версию g, которая принимает ссылку lvalue, так как в теле функции f ее параметр рассматривается как значение lvalue. При вызове функции g из функции main механизм разрешения перегрузок выбирает версию g, которая принимает ссылку rvalue, так как функция f возвращает ссылку 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()));
}

В примере получается следующий вывод.

In g(const MemoryBlock&).
In g(MemoryBlock&&).

В примере main функция передает значение rvalue fв . В теле функции f ее именованный параметр рассматривается как значение lvalue. При вызове функции f из функции g параметр связывается со ссылкой lvalue (первая перегруженная версия функции g).

  • Вы можете привести lvalue к ссылке rvalue.

Функция стандартной библиотеки std::move C++ позволяет преобразовать объект в ссылку rvalue на этот объект. Можно также использовать static_cast ключевое слово для приведения lvalue к ссылке rvalue, как показано в следующем примере:

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

В примере получается следующий вывод.

In g(const MemoryBlock&).
In g(MemoryBlock&&).

Шаблоны функций выводят типы аргументов шаблона и используют правила сортировки ссылок.

Шаблон функции, который передает параметры (или перенаправит) в другую функцию, является общим шаблоном. Важно понимать, как работает вычет типов шаблона для шаблонов функций, которые принимают ссылки rvalue.

Если аргумент функции является значением rvalue, компилятор считает, что аргумент является ссылкой rvalue. Например, предположим, что вы передаете ссылку rvalue объекту типа X в шаблон функции, который принимает тип T&& в качестве параметра. Вычет аргументов шаблона выводится TXтак, что параметр имеет тип X&&. Если аргумент функции является lvalue или const lvalue, компилятор выводит его тип в ссылку на lvalue или const ссылку lvalue этого типа.

В следующем примере объявляется один шаблон структуры, который затем специализируется для различных ссылочных типов. Функция print_type_and_value принимает в качестве параметра ссылку rvalue и пересылает ее в соответствующую специализированную версию метода S::print. Функция main показывает различные способы вызова метода 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());
}

В примере получается следующий вывод.

print<T&>: first
print<const T&>: second
print<T&&>: third
print<const T&&>: fourth

Чтобы разрешить каждый вызов print_type_and_value функции, компилятор сначала выполняет вычет аргументов шаблона. Затем компилятор применяет правила сортировки ссылок при замене типов параметров аргументами выводируемого шаблона. Например, при передаче локальной переменной s1 в функцию print_type_and_value компилятор создает следующую сигнатуру функции:

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

Компилятор использует правила сворачивания ссылок для уменьшения сигнатуры:

print_type_and_value<string&>(string& t)

Затем эта версия функции print_type_and_value пересылает свой параметр в требуемую специализированную версию метода S::print.

В следующей таблице приведены правила сворачивания ссылок для выведения типа аргументов шаблонов:

Развернутый тип Свернутый тип
T& & T&
T& && T&
T&& & T&
T&& && T&&

Вывод аргументов шаблонов — это важный элемент реализации точной пересылки. В разделе «Идеальный переадресации» подробно описано идеальное переадресация .

Итоги

Ссылки rvalue различают значения lvalue и rvalue. Чтобы повысить производительность приложений, они могут устранить необходимость в ненужных выделениях памяти и операциях копирования. Они также позволяют создавать функцию, которая принимает произвольные аргументы. Эта функция может перенаправить их в другую функцию, как если бы другая функция была вызвана напрямую.

См. также

Выражения с унарными операторами
Декларатор ссылки Lvalue: &
Lvalues и rvalues
Перемещение конструкторов и операторов назначения перемещения (C++)
Стандартная библиотека C++