T &&(双和号)在C ++ 11中意味着什么?

我一直在研究C ++ 11的一些新特性,我注意到了在声明variables中的双和号,比如T&& var

一开始,这个野兽叫什么名字? 我希望Google允许我们search这样的标点符号。

究竟是什么意思?

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

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

这里是右值引用的介绍。

以下是Microsoft标准库开发人员对rvalue引用的深入了解。 (但在阅读本文之前,请参阅此答案后的注释中的注意事项。)

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

 T&& r = T(); 

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

移动语义 。 移动构造函数和移动赋值运算符现在可以被定义为取右值引用,而不是通常的const-lvalue引用。 移动function就像一个副本,除非它不必保持源文件不变; 实际上,它通常会修改源,使其不再拥有移动的资源。 这对消除多余的副本非常有用,特别是在标准库实现中。

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

 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中,我们可以重载一个移动构造函数:

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

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

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

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

完美的转发 。 右值引用允许我们正确地为模板函数转发参数。 以此工厂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& ,即使foo的构造函数接受一个int也不会绑定到文字5。 那么,我们可以使用A1 const& ,但是如果foo通过非const引用获取构造函数参数呢? 要做一个真正的通用工厂函数,我们必须在A1&A1 const&上重载工厂。 如果工厂需要1个参数types,这可能没有问题,但是每个额外的参数types都会将必要的重载乘以2。这很快就不能维持。

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

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

 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的构造函数被调用左值。 改进的工厂function因为一个特殊的规则而起作用:

当函数参数types的forms为T&& ,其中T是模板参数,而函数参数是Atypes的左值时,typesA&被用于模板参数推导。

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

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

重要的右值引用属性

  • 对于重载parsing, 左值更喜欢绑定左值引用,右值更喜欢绑定右值引用 。 因此为什么临时variables更喜欢在复制构造函数/赋值运算符上调用移动构造函数/移动赋值运算符。
  • 右值引用将隐含地绑定到右值和作为隐式转换结果的临时值 。 即float f = 0f; int&& i = f; float f = 0f; int&& i = f; forms良好,因为float可以隐式转换为int; 该参考将是一个临时的,这是转换的结果。
  • 命名的右值引用是左值。 未命名的右值引用是右值。 这对了解std::move调用为什么是必须的: foo&& r = foo(); foo f = std::move(r); foo&& r = foo(); foo f = std::move(r);

它表示一个右值引用。 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&); 

在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&&); 

考虑这些构造函数的实现。 在第一种情况下,string必须执行副本以保留值语义,这涉及到新的堆分配。 然而,在第二种情况下,我们事先知道传入我们的构造函数的对象是立即破坏的,并且不必保持不变。 在这种情况下,我们可以有效地交换内部指针而不执行任何复制,这实际上更有效。 移动语义有利于任何昂贵的或禁止复制内部引用资源的类。 考虑一下std::unique_ptr的情况 – 现在我们的类可以区分临时对象和非临时对象,我们可以使移动语义正确工作,这样unique_ptr就不能被复制,而是可以被移动,这意味着std::unique_ptr可以合法存储在标准容器,sorting等,而C + + 03的std::auto_ptr不能。

现在我们考虑右值引用的其他用法 – 完美转发。 考虑将引用绑定到引用的问题。

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

无法回想C ++ 03对此所说的内容,但在C ++ 0x中,处理右值引用时的结果types至关重要。 对typesT的右值引用,其中T是引用types,成为typesT的引用。

 (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&& 

考虑最简单的模板函数 – 最小值和最大值。 在C ++ 03中,您必须手动重载const和non-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); } 

因为我不记得它是如何做的,但是min可以接受任何左值,右值,常值左值的组合。

T&& 与types扣除 (如完美转发) 一起使用的术语俗称为通用参考 。 这是由Scott Meyers 在这篇文章中创造的。

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

例子是:

 // template template<class T> foo(T&& t) { ... } // auto auto&& t = ...; // typedef typedef ... T; T&& t = ...; // decltype decltype(...)&& t = ...; 

请注意,标准本身没有这个概念,它只是讨论参考折叠规则,引用types推导和&&语法的(单数)组合的一种方法。

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

右值引用是一种类似于普通引用X的types,除了几个例外。 最重要的是,当涉及到函数重载parsing时,左值更喜欢老式的左值引用,而右值更喜欢新的右值引用:

 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&&) 

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

首先要比较容易理解一个例子:

  class Sample { int *ptr; // large block of memory int size; public: Sample(int sz=0) : ptr{sz != 0 ? new int[sz] : nullptr}, size{sz} {} // copy constructor that takes lvalue Sample(const Sample& s) : ptr{s.size != 0 ? new int[s.size] :\ nullptr}, size{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 ptr = new int[s.size]; size = s.size; } 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 }; 

构造函数和赋值操作符已经被重载的版本取右值引用。 右值引用允许函数在编译时(通过重载parsing)在“我被调用左值还是右值?”的条件下进行分支。 这使我们能够创build更有效率的构造函数和赋值操作符,以便移动资源而不是复制它们。

编译器自动在编译时分支(取决于是左值还是右值调用),select是否调用移动构造函数或移动赋值操作符。

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

一个实用的易于理解的例子是类模板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)通常使用std :: move完成

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

Thomas Becker的“ C ++ Rvalue References Explained”提供了一篇很好的文章,解释了所有这些和更多的内容(比如rvalues是如何实现完美的转发以及这是什么意思)。 这篇文章严重依赖他的文章。

一个简短的介绍是由Stroutrup,R。 人