右值引用声明符:&&Rvalue Reference Declarator: &&

保留对右值表达式的引用。Holds a reference to an rvalue expression.

语法Syntax

type-id && cast-expression

备注Remarks

利用右值引用,您可以将左值与右值区分开。Rvalue references enable you to distinguish an lvalue from an rvalue. 左值引用和右值引用在语法和语义上相似,但它们遵循的规则稍有不同。Lvalue references and rvalue references are syntactically and semantically similar, but they follow somewhat different rules. 有关左值和右的详细信息, 请参阅左值和右For more information about lvalues and rvalues, see Lvalues and Rvalues. 有关 lvalue 引用的详细信息, 请参阅Lvalue 引用声明符: &For more information about lvalue references, see Lvalue Reference Declarator: &.

以下各节介绍 rvalue 引用如何支持移动语义完美转发的实现。The following sections describe how rvalue references support the implementation of move semantics and perfect forwarding.

移动语义Move Semantics

右值引用支持移动语义的实现, 这可以显著提高应用程序的性能。Rvalue references support the implementation of move semantics, which can significantly increase the performance of your applications. 利用移动语义,你可以编写将资源(如动态分配的内存)从一个对象转移到另一个对象的代码。Move semantics enables you to write code that transfers resources (such as dynamically allocated memory) from one object to another. 移动语义很有效,因为它使资源能够从无法在程序中的其他位置引用的临时对象转移。Move semantics works because it enables resources to be transferred from temporary objects that cannot be referenced elsewhere in the program.

若要实现移动语义, 通常需要向类提供移动构造函数和移动赋值运算符 (运算符 = )。To implement move semantics, you typically provide a move constructor, and optionally a move assignment operator (operator=), to your class. 其源是右值的复制和赋值操作随后会自动利用移动语义。Copy and assignment operations whose sources are rvalues then automatically take advantage of move semantics. 与默认复制构造函数不同,编译器不提供默认移动构造函数。Unlike the default copy constructor, the compiler does not provide a default move constructor. 有关如何编写移动构造函数以及如何在你的应用程序中使用它的详细信息,请参阅移动构造函数和移动赋值运算符 (C++)For more information about how to write a move constructor and how to use it in your application, see Move Constructors and Move Assignment Operators (C++).

您还可以重载普通函数和运算符以利用移动语义。You can also overload ordinary functions and operators to take advantage of move semantics. Visual Studio 2010 介绍了如何将移动C++语义引入标准库。Visual Studio 2010 introduces move semantics into the C++ Standard Library. 例如,string 类实现了执行移动语义的操作。For example, the string class implements operations that perform move semantics. 请考虑以下串联几个字符串并输出结果的示例:Consider the following example that concatenates several strings and prints the result:

// 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并返回一个新的临时对象 (右值)。Before Visual Studio 2010, each call to operator+ allocates and returns a new temporary string object (an rvalue). operator + 不能将一个字符串追加到另一个字符串, 因为它不知道源字符串是左值还是右。operator+ cannot append one string to the other because it does not know whether the source strings are lvalues or rvalues. 如果两个源字符串都是左值,则它们可能会在程序中的其他位置引用,因此不能修改。If the source strings are both lvalues, they might be referenced elsewhere in the program and therefore must not be modified. 通过使用右值引用, 可以修改operator + 以获取右, 这不能在程序中的其他位置引用。By using rvalue references, operator+ can be modified to take rvalues, which cannot be referenced elsewhere in the program. 因此, operator + 现在可以将一个字符串追加到另一个字符串。Therefore, operator+ can now append one string to another. 这可以显著减少 string 类必须执行的动态内存分配的数量。This can significantly reduce the number of dynamic memory allocations that the string class must perform. 有关详细信息string类,请参阅asic_string 类For more information about the string class, see basic_string Class.

当编译器无法使用返回值优化 (RVO) 或命名返回值优化 (NRVO) 时,移动语义也很有用。Move semantics also helps when the compiler cannot use Return Value Optimization (RVO) or Named Return Value Optimization (NRVO). 在这些情况下,如果类型定义了移动构造函数,则编译器将调用该函数。In these cases, the compiler calls the move constructor if the type defines it. 有关命名返回值优化的详细信息, 请参阅Visual Studio 2005 中的命名返回值优化For more information about Named Return Value Optimization, see Named Return Value Optimization in Visual Studio 2005.

若要更好地了解移动语义,请考虑将元素插入 vector 对象的示例。To better understand move semantics, consider the example of inserting an element into a vector object. 如果超出 vector 对象的容量,则 vector 对象必须为其元素重新分配内存,然后将所有元素复制到其他内存位置,以便为插入的元素腾出空间。If the capacity of the vector object is exceeded, the vector object must reallocate memory for its elements and then copy each element to another memory location to make room for the inserted element. 当插入操作复制某个元素时,它将创建一个新元素,调用复制构造函数以将数据从上一个元素复制到新元素,然后销毁上一个元素。When an insertion operation copies an element, it creates a new element, calls the copy constructor to copy the data from the previous element to the new element, and then destroys the previous element. 利用移动语义,您可以直接移动对象而不必执行成本高昂的内存分配和复制操作。Move semantics enables you to move objects directly without having to perform expensive memory allocation and copy operations.

若要在 vector 示例中利用移动语义,则可以编写将数据从一个对象移动到另一个对象的移动构造函数。To take advantage of move semantics in the vector example, you can write a move constructor to move data from one object to another.

若要详细了解如何将移动语义引入 Visual Studio C++ 2010 中的标准库, 请参阅 C++标准库For more information about the introduction of move semantics into the C++ Standard Library in Visual Studio 2010, see C++ Standard Library.

完美转发Perfect Forwarding

完美转发可减少对重载函数的需求,并有助于避免转发问题。Perfect forwarding reduces the need for overloaded functions and helps avoid the forwarding problem. 当你编写将引用作为其参数的泛型函数并将这些参数传递 (或转发) 给另一个函数时, 可能会出现转发问题The forwarding problem can occur when you write a generic function that takes references as its parameters and it passes (or forwards) these parameters to another function. 例如,如果泛型函数采用 const T& 类型的参数,则调用的函数无法修改该参数的值。For example, if the generic function takes a parameter of type const T&, then the called function cannot modify the value of that parameter. 如果泛型函数采用 T& 类型的参数,则无法使用右值(如临时对象或整数文本)来调用该函数。If the generic function takes a parameter of type T&, then the function cannot be called by using an rvalue (such as a temporary object or integer literal).

通常,若要解决此问题,则必须提供为其每个参数采用 T&const T& 的重载版本的泛型函数。Ordinarily, to solve this problem, you must provide overloaded versions of the generic function that take both T& and const T& for each of its parameters. 因此,重载函数的数量将基于参数的数量呈指数方式增加。As a result, the number of overloaded functions increases exponentially with the number of parameters. 利用右值引用,您可以编写一个版本的函数,该函数可接受任意参数并将其转发给另一个函数,就像已直接调用其他函数一样。Rvalue references enable you to write one version of a function that accepts arbitrary arguments and forwards them to another function as if the other function had been called directly.

请考虑以下声明了四个类型 W``XYZ 的示例。Consider the following example that declares four types, W, X, Y, and Z. 每个类型的构造函数采用不同的constconst lvalue 引用组合作为其参数。The constructor for each type takes a different combination of const and non-const lvalue references as its parameters.

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

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

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

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

假定您要编写生成对象的泛型函数。Suppose you want to write a generic function that generates objects. 以下示例演示了一种编写此函数的方式:The following example shows one way to write this function:

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

以下示例演示了对 factory 函数的有效调用:The following example shows a valid call to the factory function:

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

但是,以下示例不包含对 factory 函数的有效调用,因为 factory 采用可修改的左值引用作为其参数,但该函数是使用右值调用的:However, the following example does not contain a valid call to the factory function because factory takes lvalue references that are modifiable as its parameters, but it is called by using rvalues:

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

通常,若要解决此问题,您必须为 factoryA& 的参数的每个组合创建一个重载版本的 const A& 函数。Ordinarily, to solve this problem, you must create an overloaded version of the factory function for every combination of A& and const A& parameters. 利用右值引用,您可以编写一个版本的 factory 函数,如以下示例所示:Rvalue references enable you to write one version of the factory function, as shown in the following example:

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 函数的参数。This example uses rvalue references as the parameters to the factory function. Std:: forward函数的用途是将 factory 函数的参数转发给模板类的构造函数。The purpose of the std::forward function is to forward the parameters of the factory function to the constructor of the template class.

以下示例演示了使用修改后的 main 函数创建 factoryWXY 类的实例的 Z 函数。The following example shows the main function that uses the revised factory function to create instances of the W, X, Y, and Z classes. 修改后的 factory 函数会将其参数(左值和右值)转发给适当的类构造函数。The revised factory function forwards its parameters (either lvalues or rvalues) to the appropriate class constructor.

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

右值引用的其他属性Additional Properties of Rvalue References

可以重载一个函数, 使其采用左值引用和右值引用。You can overload a function to take an lvalue reference and an rvalue reference.

通过重载某个函数以获取常量左值引用或右值引用, 你可以编写代码来区分不可修改的对象 (左值) 和可修改的临时值 (右)。By overloading a function to take a const lvalue reference or an rvalue reference, you can write code that distinguishes between non-modifiable objects (lvalues) and modifiable temporary values (rvalues). 可以将对象传递到采用右值引用的函数, 除非该对象标记为constYou can pass an object to a function that takes an rvalue reference unless the object is marked as const. 以下示例演示了函数 f,该函数将被重载以采用左值引用和右值引用。The following example shows the function f, which is overloaded to take an lvalue reference and an rvalue reference. main 函数同时使用左值和右值来调用 fThe main function calls f with both lvalues and an 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());
}

该示例产生下面的输出:This example produces the following output:

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

在此示例中,对 f 的第一个调用将局部变量(左值)作为其自变量传递。In this example, the first call to f passes a local variable (an lvalue) as its argument. f 的第二个调用将临时对象作为其自变量传递。The second call to f passes a temporary object as its argument. 由于无法在程序中的其他位置引用临时对象,因此调用将绑定到采用右值引用的重载版本的 f,该版本可以随意修改对象。Because the temporary object cannot be referenced elsewhere in the program, the call binds to the overloaded version of f that takes an rvalue reference, which is free to modify the object.

编译器将已命名的右值引用视为左值并将未命名的右值引用视为右值。The compiler treats a named rvalue reference as an lvalue and an unnamed rvalue reference as an rvalue.

当编写采用右值引用作为其参数的函数时,该参数被视为函数体中的左值。When you write a function that takes an rvalue reference as its parameter, that parameter is treated as an lvalue in the body of the function. 编译器将已命名的右值引用视为左值是因为已命名的对象可以由程序中的多个部分引用;而允许程序的多个部分在该对象中修改或移除资源是很危险的。The compiler treats a named rvalue reference as an lvalue because a named object can be referenced by several parts of a program; it would be dangerous to allow multiple parts of a program to modify or remove resources from that object. 例如,如果程序的多个部分尝试从同一对象转移资源,则只有第一个部分能成功转移该资源。For example, if multiple parts of a program try to transfer resources from the same object, only the first part will successfully transfer the resource.

以下示例演示了函数 g,该函数将被重载以采用左值引用和右值引用。The following example shows the function g, which is overloaded to take an lvalue reference and an rvalue reference. 函数 f 采用右值引用作为其参数(已命名的右值引用),并返回右值引用(未命名的右值引用)。The function f takes an rvalue reference as its parameter (a named rvalue reference) and returns an rvalue reference (an unnamed rvalue reference). 在从 gf 的调用中,重载决策选择采用左值引用的 g 版本,因为 f 的主体将其参数视为左值。In the call to g from f, overload resolution selects the version of g that takes an lvalue reference because the body of f treats its parameter as an lvalue. 在从 gmain 的调用中,重载决策选择采用右值引用的 g 版本,因为 f 返回右值引用。In the call to g from main, overload resolution selects the version of g that takes an rvalue reference because f returns an rvalue reference.

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

该示例产生下面的输出:This example produces the following output:

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

在此示例中,main 函数将右值传递给 fIn this example, the main function passes an rvalue to f. f 的主体将其命名参数视为左值。The body of f treats its named parameter as an lvalue. fg 的调用会将参数绑定到左值引用(第一个重载版本的 g)。The call from f to g binds the parameter to an lvalue reference (the first overloaded version of g).

  • 可以将左值强制转换为右值引用。You can cast an lvalue to an rvalue reference.

C++ 标准库std:: move函数使您能够将对象转换为对该对象的右值引用。The C++ Standard Library std::move function enables you to convert an object to an rvalue reference to that object. 或者, 可以使用static_cast关键字将左值强制转换为右值引用, 如以下示例中所示:Alternatively, you can use the static_cast keyword to cast an lvalue to an rvalue reference, as shown in the following example:

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

该示例产生下面的输出:This example produces the following output:

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

函数模板推导其模板参数类型, 然后使用引用折叠规则。Function templates deduce their template argument types and then use reference collapsing rules.

通常会编写一个将其参数传递 (或转发) 给另一个函数的函数模板。It is common to write a function template that passes (or forwards) its parameters to another function. 了解模板类型推导如何对采用右值引用的函数模板起作用很重要。It is important to understand how template type deduction works for function templates that take rvalue references.

如果函数参数是右值,则编译器将参数推导为右值引用。If the function argument is an rvalue, the compiler deduces the argument to be an rvalue reference. 例如,如果将对 X 类型的对象的右值引用传递给采用类型 T&& 作为其参数的模板函数,则模板自变量推导会将 T 推导为 XFor example, if you pass an rvalue reference to an object of type X to a template function that takes type T&& as its parameter, template argument deduction deduces T to be X. 因此,参数具有类型 X&&Therefore, the parameter has type X&&. 如果函数参数是 lvalue 或const左值, 则编译器会将其类型推导为左值引用或该类型的const lvalue 引用。If the function argument is an lvalue or const lvalue, the compiler deduces its type to be an lvalue reference or const lvalue reference of that type.

以下示例声明了一个结构模板,然后针对不同引用类型对其进行了专用化。The following example declares one structure template and then specializes it for various reference types. print_type_and_value 函数采用右值引用作为其参数,并将它转发给适当专用版本的 S::print 方法。The print_type_and_value function takes an rvalue reference as its parameter and forwards it to the appropriate specialized version of the S::print method. main 函数演示了调用 S::print 方法的各种方式。The main function demonstrates the various ways to call the S::print method.

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

该示例产生下面的输出:This example produces the following output:

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

为了解析对 print_type_and_value 函数的每个调用,编译器首先会执行模板自变量推导。To resolve each call to the print_type_and_value function, the compiler first performs template argument deduction. 然后,编译器在用推导出的模板自变量替换参数类型时应用引用折叠规则。The compiler then applies reference collapsing rules when it substitutes the deduced template arguments for the parameter types. 例如,将局部变量 s1 传递给 print_type_and_value 函数将导致编译器生成以下函数签名:For example, passing the local variable s1 to the print_type_and_value function causes the compiler to produce the following function signature:

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

编译器使用引用折叠规则将签名缩短为以下形式:The compiler uses reference collapsing rules to reduce the signature to the following:

print_type_and_value<string&>(string& t)

此版本的 print_type_and_value 函数随后将其参数转发到正确的专用版本的 S::print 方法。This version of the print_type_and_value function then forwards its parameter to the correct specialized version of the S::print method.

下表汇总了模板自变量类型推导的引用折叠规则:The following table summarizes the reference collapsing rules for template argument type deduction:

展开类型Expanded type 折叠类型Collapsed type
T& & T&
T& && T&
T&& & T&
T&& && T&&

模板自变量推导是实现完美转发的重要因素。Template argument deduction is an important element of implementing perfect forwarding. 本主题前面部分的“完美转发”一节更详细地介绍了完美转发。The section Perfect Forwarding, which is presented earlier in this topic, describes perfect forwarding in more detail.

总结Summary

右值引用可将左值和右值区分开。Rvalue references distinguish lvalues from rvalues. 它们可以帮助您消除不必要的内存分配和复制操作需求,从而提高应用程序的性能。They can help you improve the performance of your applications by eliminating the need for unnecessary memory allocations and copy operations. 它们还使你能够编写一个版本的函数,该函数可接受任意自变量并将其转发给另一个函数,就像已直接调用其他函数一样。They also enable you to write one version of a function that accepts arbitrary arguments and forwards them to another function as if the other function had been called directly.

请参阅See also

使用一元运算符的表达式Expressions with Unary Operators
Lvalue 引用声明符:&Lvalue Reference Declarator: &
左值和右值Lvalues and Rvalues
移动构造函数和移动赋值运算符 (C++)Move Constructors and Move Assignment Operators (C++)
C++ 标准库C++ Standard Library