Rvalue引用声明:&&

保存对 rvalue 表达式的引用。

type-id && cast-expression

备注

Rvalue 引用让您可以将 lvalues 和 rvalues 区分开来。 左值引用和右值引用在语法上和语义上是相似的,但是,它们遵循某些不同的规则。 有关左值和右值的更多信息,请参见 Lvalue和Rvalues。 有关左值的更多信息,请参见左值引用声明:&

以下各节介绍 rvalue 引用如何支持移动语义 和完全转发 的实现。

移动语义

Rvalue 引用支持“移动语义”的执行,这能够非常显著地增加应用程序性能。 移动语义可以编写调用资源从一个对象到另一个的代码(例如动态分配的内存)。 移动语义工作,因为它使资源可以从程序中的其他位置无法引用的临时对象转移。

若要实现移动语义,您通常会向您的类提供 移动构造函数和可选择将赋值运算符 (operator=)。 副本和分配操作的数据源是 rvalues, 然后利用移动语义。 不同于默认复制构造函数,编译器不提供默认移动构造函数。 有关如何编写移动构造函数以及如何在您的应用程序中使用的更多信息,请参见 如何:编写一个移动构造函数

您还可以重载一般函数和运算符,以利用移动语义。 Visual C++ 2010 介绍移动语义到标准模板库 (STL)。 例如, 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 C++ 2010 之前,每个都应调用 operator+ 分配且应返回到新的临时对象 string(左值)。 operator+ 不能追加一个字符串到其他,因为不知道源字符串是否是 lvalue 或 rvalues。 如果源字符串都是左值,则它们可能在程序其他位置被引用并不可修改。 通过使用右值引用,可以修改 operator+ 采用右值,其中右值不能在程序的其他位置引用。 因此, operator+ 现在可添加一个字符串到另一个。 这样可以显著减少 string 类必须执行的动态内存分配数。 有关 string 类的更多信息,请参见basic_string Class

当该编译器无法使用回归值优化(RVO)或名为回归值优化时(NRVO),移动语义也帮助。 在这些情况下,如果类型定义它,该编译器则调用移动构造函数。 有关命名返回值优化的更多信息,请参见 Visual C++ 2005 中的命名返回值优化

更好地了解移动语义,请考虑插入元素的示例 vector 对象。 如果超出 vector 对象的容量,则 vector 对象必须重新分配其元素的内存然后复制每个元素到另一个内存位置,以便为插入的元素腾出空间。 插入操作复制某个元素时,其将创建一个新元素、调用复制构造函数,以将数据从上一个元素复制到新元素,然后销毁上一个元素。 移动语义可以将对象直接移动,而不必执行开销很大的内存分配和复制操作。

要在 vector 示例中利用移动语义,可以对从一个对象的数据移动编写移动到另一个构造函数。

有关移动语义进入 Visual C++ 2010的 STL 中的介绍的更多详细信息,请参见 标准C++库参考

完全转发

完全转发减少对重载函数的需求,并有助于避免转发问题。 当您编写采用引用的泛型函数作为其参数,且将这些参数传递(或“向前”)给另一个函数时,则会引发“转发问题”。 例如,如果泛型函数采用类型 const T& 的参数,则被调用函数无法修改该参数的值。 如果泛型函数采用类型 T&的参数,则不能通过使用 rvalue (如临时对象或整数值)来调用函数。

通常,解决此问题,必须提供泛型函数的重载版本,此函数采用 T& 和 const T& 作为其参数。 因此,重载函数的数目与参数的数目呈指数方式增加。 Rvalue 引用让您能够写入一个版本的函数,后者接受任意参数并且能将这些参数转发给其他的函数,就像直接调用了另外那个函数一样。

考虑以下声明了四个类型的示例,WX、Y 和 Z 每个类型的构造函数采用 const 和非 const 左值引用的不同组合作为其参数。

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 函数的参数的值引用。 std::forward 函数的用途使工厂函数的参数向前倒模板类的构造函数。

以下示例显示使用修改的 factory 函数创建 W、 X、Y 和 Z 类的实例的 main 函数。 重新修订了的 factory 函数使其参数 (lvalue 或 rvalues) 向前到适当的类构造函数。

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

“右值引用”的“附加属性”

可以重载采用左值引用和右值引用的函数。

通过重载函数来采用const左值引用或右值引用,您可以编写能区分不能更改的对象 (lvalue) 和可修改的临时值 (rvalues) 的代码。 可以将对象传递给函数,该函数在该对象未标记为 const 的情况下采用右值引用。 下面的示例演示了重载函数 f 如何使用 lvalue 引用和 rvalue 引用。 main 函数通过 lvalue 和rvalue 调用 f。

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

该示例产生下面的输出:

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

在此示例中,f 的第一个调用传递本地变量(左值)作为其参数。 为 f 的第二次传递一个临时对象作为其参数。 由于无法在程序的其他位置引用临时对象,因此将调用绑定到自由修改对象的f 重载版本中。

该编译器将已命名的rvalue 引用视为左值而将未命名的 rvalue 引用视为rvalue。

当写入采用 rvalue 引用作为其参数的函数时,该参数将视为该函数体中的 lvalue。 该编译器将已命名的rvalue引用视为lvalue是因为已命名的对象可以被程序中多个部分引用;而允许程序的多个部件在该对象中修改或移除资源是危险的。 例如,如果程序的多个部分尝试从同一个对象调用资源,只有第一部分成功调用该资源。

下面的示例演示了重载函数 g 如何使用 lvalue 引用和 rvalue 引用。 函数 f 采用 rvalue 引用作为其参数(名为 rvalue 引用),并返回 rvalue 引用(未命名的 rvalue 引用)。 在从 g 调用的 f中,因为 f 正文将其参数视为左值,重载解决方案选择采用左值引用的 g 版本。 在从 main中调用的g 中,因为 f 返回引用左值,重载解决方案选择采用左值引用的 g 版本。

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

该示例产生下面的输出:

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

在此示例中,main 函数传递时间间隔给 f。 f 体将其命名参数用作左值。 从 f 到 g 的调用将参数和左值引用(g的第一个重载版本)连接起来。

  • 您可以将左值转换为左值引用。

STL std::move 函数使您能够将对象转换为该对象的 rvalue 引用。 或者,如下面的示例所示,可以使用 static_cast 关键字转换左值到左值引用:

// 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 引用。 例如,如果通过在类型 X 对象的 rvalue 上引用参考函数,使 T&& 成为其将参数的模板函数,模板参数推导 T 是 X。 因此,参数具有类型 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 引用将 lvalues 和 rvalues 区分开来。 它们可帮助您通过消除提升无用的内存分配和复制操作的需求来提升您的应用程序的性能。 它们也使您写入接受任意参数的函数的一个版本和使它们向前到另一个函数,好像另一个函数已被直接调用。

请参见

任务

如何:编写一个移动构造函数

参考

使用一元运算符的表达式

左值引用声明:&

Lvalue和Rvalues

move

forward

其他资源

标准C++库参考