小编典典

C++11 中的 T&&(双 & 符号)是什么意思?

all

我一直在研究 C++11 的一些新特性,我注意到其中一个是声明变量时的双 & 符号,例如T&& var.

首先,这个野兽叫什么?我希望谷歌允许我们搜索这样的标点符号。

它到底是什么 意思?

乍一看,它似乎是一个双重引用(如 C 风格的双指针T** var),但我很难想到一个用例。


阅读 412

收藏
2022-02-28

共1个答案

小编典典

它声明了一个右值引用(标准提案文档)。

这是对右值引用的介绍。

这是 Microsoft
的标准库开发人员
之一对右值引用的精彩深入了解。

注意: MSDN 上的链接文章(“Rvalue References: C0x Features in VC10, Part 2”)是对
Rvalue 引用的非常清晰的介绍,但对 Rvalue 引用的陈述在 C
11
草案中曾经是正确的标准,但不是最后一个!具体来说,它在不同的点上说右值引用可以绑定到左值,这曾经是正确的,但被改变了。(例如 int x; int
&&rrx = x; 不再在 GCC 中编译)-drawbarbs 2014 年 7 月 13 日 16 点: 12

C03 引用(现在在 C11 中称为左值引用)之间的最大区别在于它可以像临时一样绑定到右值,而不必是 const。因此,这种语法现在是合法的:

T&& r = T();

右值引用主要提供以下内容:

移动语义 。现在可以定义一个移动构造函数和移动赋值运算符,它采用右值引用而不是通常的 const-lvalue
引用。移动的功能类似于副本,但不必保持源不变;事实上,它通常会修改源,使其不再拥有移动的资源。这对于消除无关副本非常有用,尤其是在标准库实现中。

例如,复制构造函数可能如下所示:

foo(foo const& other)
{
    this->length = other.length;
    this->ptr = new int[other.length];
    copy(other.ptr, other.ptr + other.length, this->ptr);
}

如果这个构造函数被传递了一个临时的,副本将是不必要的,因为我们知道临时将被销毁;为什么不利用临时已经分配的资源呢?在 C03
中,没有办法阻止复制,因为我们无法确定我们是否传递了一个临时的。在 C
11 中,我们可以重载移动构造函数:

foo(foo&& other)
{
   this->length = other.length;
   this->ptr = other.ptr;
   other.length = 0;
   other.ptr = nullptr;
}

请注意这里的最大区别:移动构造函数实际上修改了它的参数。这将有效地将临时“移动”到正在构造的对象中,从而消除不必要的副本。

移动构造函数将用于临时对象和非常量左值引用,这些引用使用std::move函数显式转换为右值引用(它只是执行转换)。以下代码都为f1and调用移动构造函数f2

foo f1((foo())); // Move a temporary into f1; temporary becomes "empty"
foo f2 = std::move(f1); // Move f1 into f2; f1 is now "empty"

完美转发 。右值引用允许我们正确地转发模板函数的参数。以这个工厂函数为例:

template <typename T, typename A1>
std::unique_ptr<T> factory(A1& a1)
{
    return std::unique_ptr<T>(new T(a1));
}

如果我们调用factory<foo>(5),则参数将被推导出为int&,即使foo的构造函数采用,它也不会绑定到文字 5
int。好吧,我们可以改为使用A1 const&,但是如果foo通过非常量引用获取构造函数参数怎么办?要创建一个真正通用的工厂函数,我们必须重载
factory onA1&和 on A1 const&。如果 factory 采用 1
个参数类型,这可能很好,但每个额外的参数类型都会将必要的重载集乘以 2。这很快就无法维护。

右值引用通过允许标准库定义一个std::forward可以正确转发左值/右值引用的函数来解决这个问题。有关如何std::forward工作的更多信息,请参阅这个出色的答案

这使我们能够像这样定义工厂函数:

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

现在,当传递给T‘ 的构造函数时,参数的 rvalue/lvalue-ness 被保留。这意味着如果使用右值调用 factory,则使用右值T调用
的构造函数。如果使用左值调用 factory,则使用左值T调用 的构造函数。改进的工厂函数之所以起作用,是因为有一个特殊规则:

当函数形参类型是模板形参的形式T&&T并且函数实参是 type 的左值时A,该类型A&用于模板实参推导。

因此,我们可以像这样使用工厂:

auto p1 = factory<foo>(foo()); // calls foo(foo&&)
auto p2 = factory<foo>(*p1);   // calls foo(foo const&)

重要的右值参考属性

  • 对于重载决议, 左值更喜欢绑定到左值引用,而右值更喜欢绑定到右值引用 。因此,为什么临时工更喜欢调用移动构造函数/移动赋值运算符而不是复制构造函数/赋值运算符。
  • 右值引用将隐式绑定到右值和作为隐式转换结果的临时值 。iefloat f = 0f; int&& i = f;格式正确,因为 float 可以隐式转换为 int;引用将是一个临时的,它是转换的结果。
  • 命名的右值引用是左值。 未命名的右值引用是右值。 这对于理解为什么在以下std::move情况下需要调用很重要:foo&& r = foo(); foo f = std::move(r);
2022-02-28