我一直在研究 C++11 的一些新特性,我注意到其中一个是声明变量时的双 & 符号,例如T&& var.
T&& var
首先,这个野兽叫什么?我希望谷歌允许我们搜索这样的标点符号。
它到底是什么 意思?
乍一看,它似乎是一个双重引用(如 C 风格的双指针T** var),但我很难想到一个用例。
T** var
它声明了一个右值引用(标准提案文档)。
这是对右值引用的介绍。
这是 Microsoft 的标准库开发人员之一对右值引用的精彩深入了解。
注意: MSDN 上的链接文章(“Rvalue References: C0x Features in VC10, Part 2”)是对 Rvalue 引用的非常清晰的介绍,但对 Rvalue 引用的陈述在 C11 草案中曾经是正确的标准,但不是最后一个!具体来说,它在不同的点上说右值引用可以绑定到左值,这曾经是正确的,但被改变了。(例如 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 中,没有办法阻止复制,因为我们无法确定我们是否传递了一个临时的。在 C11 中,我们可以重载移动构造函数:
foo(foo&& other) { this->length = other.length; this->ptr = other.ptr; other.length = 0; other.ptr = nullptr; }
请注意这里的最大区别:移动构造函数实际上修改了它的参数。这将有效地将临时“移动”到正在构造的对象中,从而消除不必要的副本。
移动构造函数将用于临时对象和非常量左值引用,这些引用使用std::move函数显式转换为右值引用(它只是执行转换)。以下代码都为f1and调用移动构造函数f2:
std::move
f1
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。这很快就无法维护。
factory<foo>(5)
int&
foo
int
A1 const&
A1&
右值引用通过允许标准库定义一个std::forward可以正确转发左值/右值引用的函数来解决这个问题。有关如何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&&,T并且函数实参是 type 的左值时A,该类型A&用于模板实参推导。
T&&
A
A&
因此,我们可以像这样使用工厂:
auto p1 = factory<foo>(foo()); // calls foo(foo&&) auto p2 = factory<foo>(*p1); // calls foo(foo const&)
重要的右值参考属性 :
float f = 0f; int&& i = f;
foo&& r = foo(); foo f = std::move(r);