问题描述
我一直在研究 C++11 的一些新特性,我注意到其中一个是声明变量时的双 & 符号,例如 T&&变量
.
I've been looking into some of the new features of C++11 and one I've noticed is the double ampersand in declaring variables, like T&& var
.
首先,这个野兽叫什么?我希望 Google 允许我们搜索这样的标点符号.
For a start, what is this beast called? I wish Google would allow us to search for punctuation like this.
具体是什么意思?
乍一看,它似乎是一个双重引用(就像 C 风格的双指针 T** var
),但我很难考虑它的用例.
At first glance, it appears to be a double reference (like the C-style double pointers T** var
), but I'm having a hard time thinking of a use case for that.
推荐答案
它声明了一个 右值参考(标准提案文档).
It declares an rvalue reference (standards proposal doc).
这里是rvalue的介绍references.
Here's an introduction to rvalue references.
这是微软标准库之一对右值引用的精彩深入了解 开发人员.
Here's a fantastic in-depth look at rvalue references by one of Microsoft's standard library developers.
注意: MSDN 上的链接文章(Rvalue References: C++0x Features in VC10, Part 2")是对 Rvalue 引用的非常清晰的介绍,但做了陈述关于在 C++11 标准草案中曾经是正确的 Rvalue 引用,但在最终的标准中不是正确的!具体来说,它在不同的点上说右值引用可以绑定到左值,这曾经是正确的,但被改变了.(例如 int x; int &&rrx = x; 不再在 GCC 中编译)–drawbarbs 2014 年 7 月 13 日16:12
CAUTION: the linked article on MSDN ("Rvalue References: C++0x Features in VC10, Part 2") is a very clear introduction to Rvalue references, but makes statements about Rvalue references that were once true in the draft C++11 standard, but are not true for the final one! Specifically, it says at various points that rvalue references can bind to lvalues, which was once true, but was changed.(e.g. int x; int &&rrx = x; no longer compiles in GCC) – drewbarbs Jul 13 '14 at 16:12
C++03 引用(现在在 C++11 中称为左值引用)之间的最大区别在于它可以像临时值一样绑定到右值,而不必是 const.因此,这种语法现在是合法的:
The biggest difference between a C++03 reference (now called an lvalue reference in C++11) is that it can bind to an rvalue like a temporary without having to be const. Thus, this syntax is now legal:
T&& r = T();
右值引用主要提供以下功能:
rvalue references primarily provide for the following:
移动语义.现在可以定义一个移动构造函数和移动赋值运算符,它采用右值引用而不是通常的 const-lvalue 引用.移动的功能类似于副本,但不必保持源不变;事实上,它通常会修改源,使其不再拥有移动的资源.这对于消除无关副本非常有用,尤其是在标准库实现中.
Move semantics. A move constructor and move assignment operator can now be defined that takes an rvalue reference instead of the usual const-lvalue reference. A move functions like a copy, except it is not obliged to keep the source unchanged; in fact, it usually modifies the source such that it no longer owns the moved resources. This is great for eliminating extraneous copies, especially in standard library implementations.
例如,复制构造函数可能如下所示:
For example, a copy constructor might look like this:
foo(foo const& other)
{
this->length = other.length;
this->ptr = new int[other.length];
copy(other.ptr, other.ptr + other.length, this->ptr);
}
如果这个构造函数被传递了一个临时的,副本将是不必要的,因为我们知道临时将被销毁;为什么不利用临时已经分配的资源呢?在 C++03 中,没有办法阻止复制,因为我们无法确定我们是否传递了一个临时的.在 C++11 中,我们可以重载移动构造函数:
If this constructor were passed a temporary, the copy would be unnecessary because we know the temporary will just be destroyed; why not make use of the resources the temporary already allocated? In C++03, there's no way to prevent the copy as we cannot determine whether we were passed a temporary. In C++11, we can overload a move constructor:
foo(foo&& other)
{
this->length = other.length;
this->ptr = other.ptr;
other.length = 0;
other.ptr = nullptr;
}
请注意这里的最大区别:移动构造函数实际上修改了它的参数.这将有效地移动"将临时插入到正在构造的对象中,从而消除了不必要的复制.
Notice the big difference here: the move constructor actually modifies its argument. This would effectively "move" the temporary into the object being constructed, thereby eliminating the unnecessary copy.
移动构造函数将用于临时对象和非常量左值引用,这些引用使用 std::move
函数显式转换为右值引用(它只是执行转换).以下代码均调用 f1
和 f2
的移动构造函数:
The move constructor would be used for temporaries and for non-const lvalue references that are explicitly converted to rvalue references using the std::move
function (it just performs the conversion). The following code both invoke the move constructor for f1
and 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"
完美转发.右值引用允许我们正确地转发模板函数的参数.以这个工厂函数为例:
Perfect forwarding. rvalue references allow us to properly forward arguments for templated functions. Take for example this factory function:
template <typename T, typename A1>
std::unique_ptr<T> factory(A1& a1)
{
return std::unique_ptr<T>(new T(a1));
}
如果我们调用 factory<foo>(5)
,参数将被推导出为 int&
,它不会绑定到文字 5,即使 foo
的构造函数采用 int
.好吧,我们可以改为使用 A1 const&
,但是如果 foo
通过非 const 引用获取构造函数参数怎么办?要创建一个真正通用的工厂函数,我们必须在 A1&
和 A1 const&
上重载工厂.如果 factory 采用 1 个参数类型,这可能没问题,但每个额外的参数类型都会将必要的重载集乘以 2.这很快就无法维护.
If we called factory<foo>(5)
, the argument will be deduced to be int&
, which will not bind to a literal 5, even if foo
's constructor takes an int
. Well, we could instead use A1 const&
, but what if foo
takes the constructor argument by non-const reference? To make a truly generic factory function, we would have to overload factory on A1&
and on A1 const&
. That might be fine if factory takes 1 parameter type, but each additional parameter type would multiply the necessary overload set by 2. That's very quickly unmaintainable.
右值引用通过允许标准库定义一个可以正确转发左值/右值引用的 std::forward
函数来解决这个问题.有关 std::forward
工作原理的更多信息,请参阅这个出色的答案.
rvalue references fix this problem by allowing the standard library to define a std::forward
function that can properly forward lvalue/rvalue references. For more information about how std::forward
works, see this excellent answer.
这使我们能够像这样定义工厂函数:
This enables us to define the factory function like this:
template <typename T, typename A1>
std::unique_ptr<T> factory(A1&& a1)
{
return std::unique_ptr<T>(new T(std::forward<A1>(a1)));
}
现在参数的右值/左值在传递给 T
的构造函数时被保留.这意味着如果使用右值调用工厂,则使用右值调用 T
的构造函数.如果使用左值调用工厂,则使用左值调用 T
的构造函数.改进后的工厂函数之所以起作用,是因为有一个特殊规则:
Now the argument's rvalue/lvalue-ness is preserved when passed to T
's constructor. That means that if factory is called with an rvalue, T
's constructor is called with an rvalue. If factory is called with an lvalue, T
's constructor is called with an lvalue. The improved factory function works because of one special rule:
当函数参数类型为T&&
形式,其中 T
是一个模板参数和函数参数是 A
类型的左值,A&
类型是用于模板参数推导.
When the function parameter type is of the form
T&&
whereT
is a template parameter, and the function argument is an lvalue of typeA
, the typeA&
is used for template argument deduction.
因此,我们可以像这样使用工厂:
Thus, we can use factory like so:
auto p1 = factory<foo>(foo()); // calls foo(foo&&)
auto p2 = factory<foo>(*p1); // calls foo(foo const&)
重要的右值引用属性:
- 对于重载决议,左值更喜欢绑定到左值引用,而右值更喜欢绑定到右值引用.因此,为什么临时工更喜欢调用移动构造函数/移动赋值运算符而不是复制构造函数/赋值运算符.
- 右值引用将隐式绑定到右值和作为隐式转换结果的临时变量.即
float f = 0f;int&&i = f;
格式正确,因为 float 可以隐式转换为 int;引用将指向作为转换结果的临时对象. - 命名的右值引用是左值.未命名的右值引用是右值. 这对于理解为什么
std::move
调用在以下情况下是必要的很重要:foo&&r = foo();foo f = std::move(r);
- For overload resolution, lvalues prefer binding to lvalue references and rvalues prefer binding to rvalue references. Hence why temporaries prefer invoking a move constructor / move assignment operator over a copy constructor / assignment operator.
- rvalue references will implicitly bind to rvalues and to temporaries that are the result of an implicit conversion. i.e.
float f = 0f; int&& i = f;
is well formed because float is implicitly convertible to int; the reference would be to a temporary that is the result of the conversion. - Named rvalue references are lvalues. Unnamed rvalue references are rvalues. This is important to understand why the
std::move
call is necessary in:foo&& r = foo(); foo f = std::move(r);
这篇关于什么是T&&(双与号)在 C++11 中是什么意思?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!