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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5481539/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 18:15:20  来源:igfitidea点击:

What does T&& (double ampersand) mean in C++11?

c++c++11rvalue-referencec++-faqperfect-forwarding

提问by paxdiablo

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.

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

For a start, what is this beast called? I wish Google would allow us to search for punctuation like this.

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

What exactly does it mean?

究竟是什么意思?

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.

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

采纳答案by Peter Huene

It declares an rvalue reference(standards proposal doc).

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

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.

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

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

注意:MSDN 上的链接文章(“Rvalue 引用:VC10 中的 C++0x 特性,第 2 部分”)是对 Rvalue 引用的非常清楚的介绍,但对 C++11 草案中曾经正确的 Rvalue 引用进行了陈述标准,但不是最后一个!具体来说,它在各个方面都说右值引用可以绑定到左值,这曾经是正确的,但已更改。(例如 int x; int &&rrx = x; 不再在 GCC 中编译)–drewbarbs 2014 年 7 月 13 日 16:12

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:

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

T&& r = T();

rvalue references primarily provide for the following:

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

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.

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

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

If this constructor was 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 we were passed a temporary. In C++11, we can overload a move constructor:

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

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.

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

The move constructor would be used for temporaries and for non-const lvalue references that are explicitly converted to rvalue references using the std::movefunction (it just performs the conversion). The following code both invoke the move constructor for 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"

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

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 footakes 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.

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

rvalue references fix this problem by allowing the standard library to define a std::forwardfunction that can properly forward lvalue/rvalue references. For more information about how std::forwardworks, see this excellent answer.

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

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

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的构造函数时被保留。这意味着如果用右值调用 factory,则用右值T调用 的构造函数。如果用左值调用 factory,则用左值调用T的构造函数。改进后的工厂函数由于一条特殊规则而起作用:

When the function parameter type is of the form T&&where Tis a template parameter, and the function argument is an lvalue of type A, the type A&is used for template argument deduction.

当函数参数类型为T&&where Tis 模板参数形式,且函数参数为 type 左值时A,该类型A&用于模板参数推导。

Thus, we can use factory like so:

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

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

Important rvalue reference properties:

重要的右值引用属性

  • 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::movecall is necessary in: foo&& r = foo(); foo f = std::move(r);
  • 对于重载解析,左值更喜欢绑定到左值引用,而右值更喜欢绑定到右值引用。因此,为什么临时对象更喜欢调用移动构造函数/移动赋值运算符而不是复制构造函数/赋值运算符。
  • 右值引用将隐式绑定到右值和作为隐式转换结果的临时变量。iefloat f = 0f; int&& i = f;格式良好,因为 float 可以隐式转换为 int;引用将是一个临时的,它是转换的结果。
  • 命名的右值引用是左值。未命名的右值引用是右值。这对于理解为什么std::move调用是必要的很重要:foo&& r = foo(); foo f = std::move(r);

回答by Puppy

It denotes an rvalue reference. Rvalue references will only bind to temporary objects, unless explicitly generated otherwise. They are used to make objects much more efficient under certain circumstances, and to provide a facility known as perfect forwarding, which greatly simplifies template code.

它表示一个右值引用。除非明确生成,否则右值引用只会绑定到临时对象。它们用于在某些情况下使对象更有效率,并提供一种称为完美转发的工具,这大大简化了模板代码。

In C++03, you can't distinguish between a copy of a non-mutable lvalue and an rvalue.

在 C++03 中,您无法区分非可变左值和右值的副本。

std::string s;
std::string another(s);           // calls std::string(const std::string&);
std::string more(std::string(s)); // calls std::string(const std::string&);

In C++0x, this is not the case.

在 C++0x 中,情况并非如此。

std::string s;
std::string another(s);           // calls std::string(const std::string&);
std::string more(std::string(s)); // calls std::string(std::string&&);

Consider the implementation behind these constructors. In the first case, the string has to perform a copy to retain value semantics, which involves a new heap allocation. However, in the second case, we know in advance that the object which was passed in to our constructor is immediately due for destruction, and it doesn't have to remain untouched. We can effectively just swap the internal pointers and not perform any copying at all in this scenario, which is substantially more efficient. Move semantics benefit any class which has expensive or prohibited copying of internally referenced resources. Consider the case of std::unique_ptr- now that our class can distinguish between temporaries and non-temporaries, we can make the move semantics work correctly so that the unique_ptrcannot be copied but can be moved, which means that std::unique_ptrcan be legally stored in Standard containers, sorted, etc, whereas C++03's std::auto_ptrcannot.

考虑这些构造函数背后的实现。在第一种情况下,字符串必须执行复制以保留值语义,这涉及新的堆分配。然而,在第二种情况下,我们事先知道传递给我们的构造函数的对象将立即被销毁,并且它不必保持不变。在这种情况下,我们可以有效地只交换内部指针而不执行任何复制,这实际上效率更高。移动语义有益于任何具有昂贵或禁止复制内部引用资源的类。考虑这样的情况std::unique_ptr- 现在我们的类可以区分临时和非临时,我们可以使移动语义正确工作,以便unique_ptr不能复制但可以移动,这意味着std::unique_ptr可以合法地存储在标准容器中、排序等,而 C++03std::auto_ptr则不能。

Now we consider the other use of rvalue references- perfect forwarding. Consider the question of binding a reference to a reference.

现在我们考虑右值引用的另一个用途——完美转发。考虑将引用绑定到引用的问题。

std::string s;
std::string& ref = s;
(std::string&)& anotherref = ref; // usually expressed via template

Can't recall what C++03 says about this, but in C++0x, the resultant type when dealing with rvalue references is critical. An rvalue reference to a type T, where T is a reference type, becomes a reference of type T.

不记得 C++03 对此是怎么说的,但在 C++0x 中,处理右值引用时的结果类型至关重要。对类型 T 的右值引用(其中 T 是引用类型)成为类型 T 的引用。

(std::string&)&& ref // ref is std::string&
(const std::string&)&& ref // ref is const std::string&
(std::string&&)&& ref // ref is std::string&&
(const std::string&&)&& ref // ref is const std::string&&

Consider the simplest template function- min and max. In C++03 you have to overload for all four combinations of const and non-const manually. In C++0x it's just one overload. Combined with variadic templates, this enables perfect forwarding.

考虑最简单的模板函数——min 和 max。在 C++03 中,您必须手动重载 const 和非常量的所有四种组合。在 C++0x 中,它只是一种重载。结合可变参数模板,这可以实现完美转发。

template<typename A, typename B> auto min(A&& aref, B&& bref) {
    // for example, if you pass a const std::string& as first argument,
    // then A becomes const std::string& and by extension, aref becomes
    // const std::string&, completely maintaining it's type information.
    if (std::forward<A>(aref) < std::forward<B>(bref))
        return std::forward<A>(aref);
    else
        return std::forward<B>(bref);
}

I left off the return type deduction, because I can't recall how it's done offhand, but that min can accept any combination of lvalues, rvalues, const lvalues.

我省略了返回类型推导,因为我不记得它是如何临时完成的,但是 min 可以接受左值、右值、常量左值的任何组合。

回答by mmocny

The term for T&&when used with type deduction(such as for perfect forwarding) is known colloquially as a forwarding reference. The term "universal reference" was coined by Scott Meyers in this article, but was later changed.

T&&与类型推导(例如完美转发)一起使用时的术语通俗地称为转发引用。“通用参考”一词是 Scott Meyers在这篇文章中创造的,但后来有所改变。

That is because it may be either r-value or l-value.

那是因为它可能是 r 值或 l 值。

Examples are:

例子是:

// template
template<class T> foo(T&& t) { ... }

// auto
auto&& t = ...;

// typedef
typedef ... T;
T&& t = ...;

// decltype
decltype(...)&& t = ...;

More discussion can be found in the answer for: Syntax for universal references

更多讨论可以在以下答案中找到:通用引用的语法

回答by kurt krueckeberg

An rvalue reference is a type that behaves much like the ordinary reference X&, with several exceptions. The most important one is that when it comes to function overload resolution, lvalues prefer old-style lvalue references, whereas rvalues prefer the new rvalue references:

右值引用是一种行为很像普通引用 X& 的类型,但有几个例外。最重要的一点是,当涉及到函数重载解析时,左值更喜欢旧式左值引用,而右值更喜欢新式右值引用:

void foo(X& x);  // lvalue reference overload
void foo(X&& x); // rvalue reference overload

X x;
X foobar();

foo(x);        // argument is lvalue: calls foo(X&)
foo(foobar()); // argument is rvalue: calls foo(X&&)

So what is an rvalue? Anything that is not an lvalue. An lvalue being an expression that refers to a memory location and allows us to take the address of that memory location via the & operator.

那么什么是右值呢?任何不是左值的东西。左值是一个引用内存位置的表达式,并允许我们通过 & 运算符获取该内存位置的地址。

It is almost easier to understand first what rvalues accomplish with an example:

首先通过一个例子来理解右值的作用几乎更容易:

 #include <cstring>
 class Sample {
  int *ptr; // large block of memory
  int size;
 public:
  Sample(int sz=0) : ptr{sz != 0 ? new int[sz] : nullptr}, size{sz} 
  {
     if (ptr != nullptr) memset(ptr, 0, sz);
  }
  // copy constructor that takes lvalue 
  Sample(const Sample& s) : ptr{s.size != 0 ? new int[s.size] :\
      nullptr}, size{s.size}
  {
     if (ptr != nullptr) memcpy(ptr, s.ptr, s.size);
     std::cout << "copy constructor called on lvalue\n";
  }

  // move constructor that take rvalue
  Sample(Sample&& s) 
  {  // steal s's resources
     ptr = s.ptr;
     size = s.size;        
     s.ptr = nullptr; // destructive write
     s.size = 0;
     cout << "Move constructor called on rvalue." << std::endl;
  }    
  // normal copy assignment operator taking lvalue
  Sample& operator=(const Sample& s)
  {
   if(this != &s) {
      delete [] ptr; // free current pointer
      size = s.size;

      if (size != 0) {
        ptr = new int[s.size];
        memcpy(ptr, s.ptr, s.size);
      } else 
         ptr = nullptr;
     }
     cout << "Copy Assignment called on lvalue." << std::endl;
     return *this;
  }    
 // overloaded move assignment operator taking rvalue
 Sample& operator=(Sample&& lhs)
 {
   if(this != &s) {
      delete [] ptr; //don't let ptr be orphaned 
      ptr = lhs.ptr;   //but now "steal" lhs, don't clone it.
      size = lhs.size; 
      lhs.ptr = nullptr; // lhs's new "stolen" state
      lhs.size = 0;
   }
   cout << "Move Assignment called on rvalue" << std::endl;
   return *this;
 }
//...snip
};     

The constructor and assignment operators have been overloaded with versions that take rvalue references. Rvalue references allow a function to branch at compile time (via overload resolution) on the condition "Am I being called on an lvalue or an rvalue?". This allowed us to create more efficient constructor and assignment operators above that move resources rather copy them.

构造函数和赋值运算符已被采用右值引用的版本重载。右值引用允许函数在编译时(通过重载解析)在条件“我被左值还是右值调用?”的情况下进行分支。这允许我们在上面创建更高效​​的构造函数和赋值运算符来移动资源而不是复制它们。

The compiler automatically branches at compile time (depending on the whether it is being invoked for an lvalue or an rvalue) choosing whether the move constructor or move assignment operator should be called.

编译器在编译时自动分支(取决于它是为左值还是右值调用)选择是否应调用移动构造函数或移动赋值运算符。

Summing up: rvalue references allow move semantics (and perfect forwarding, discussed in the article link below).

总结:右值引用允许移动语义(和完美转发,在下面的文章链接中讨论)。

One practical easy-to-understand example is the class template std::unique_ptr. Since a unique_ptr maintains exclusive ownership of its underlying raw pointer, unique_ptr's can't be copied. That would violate their invariant of exclusive ownership. So they do not have copy constructors. But they do have move constructors:

一个实用且易于理解的示例是类模板std::unique_ptr。由于 unique_ptr 维护其底层原始指针的独占所有权,因此无法复制 unique_ptr。这将违反他们的独占所有权不变式。所以他们没有复制构造函数。但他们确实有移动构造函数:

template<class T> class unique_ptr {
  //...snip
 unique_ptr(unique_ptr&& __u) noexcept; // move constructor
};

 std::unique_ptr<int[] pt1{new int[10]};  
 std::unique_ptr<int[]> ptr2{ptr1};// compile error: no copy ctor.  

 // So we must first cast ptr1 to an rvalue 
 std::unique_ptr<int[]> ptr2{std::move(ptr1)};  

std::unique_ptr<int[]> TakeOwnershipAndAlter(std::unique_ptr<int[]> param,\
 int size)      
{
  for (auto i = 0; i < size; ++i) {
     param[i] += 10;
  }
  return param; // implicitly calls unique_ptr(unique_ptr&&)
}

// Now use function     
unique_ptr<int[]> ptr{new int[10]};

// first cast ptr from lvalue to rvalue
unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(\
           static_cast<unique_ptr<int[]>&&>(ptr), 10);

cout << "output:\n";

for(auto i = 0; i< 10; ++i) {
   cout << new_owner[i] << ", ";
}

output:
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 

static_cast<unique_ptr<int[]>&&>(ptr)is usually done using std::move

static_cast<unique_ptr<int[]>&&>(ptr)通常使用std::move 完成

// first cast ptr from lvalue to rvalue
unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(std::move(ptr),0);

An excellent article explaining all this and more (like how rvalues allow perfect forwarding and what that means) with lots of good examples is Thomas Becker's C++ Rvalue References Explained. This post relied heavily on his article.

Thomas Becker 的C++ Rvalue References Explained是一篇很好的文章,解释了所有这些以及更多(例如右值如何允许完美转发以及这意味着什么),并提供了许多很好的示例。这篇文章在很大程度上依赖于他的文章。

A shorter introduction is A Brief Introduction to Rvalue Referencesby Stroutrup, et. al

更简短的介绍是Stroutrup 等人的A Brief Introduction to Rvalue References。阿尔