什么是T&&(双与号)在 C++11 中是什么意思? [英] What does T&& (double ampersand) mean in C++11?

查看:39
本文介绍了什么是T&&(双与号)在 C++11 中是什么意思?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在研究 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 函数显式转换为右值引用(它只是执行转换).以下代码均调用 f1f2 的移动构造函数:

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&& where T is a template parameter, and the function argument is an lvalue of type A, the type A& 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&amp;&amp;(双与号)在 C++11 中是什么意思?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆