我一直在研究C ++ 11的一些新功能,我注意到的一个是在声明变量(例如T&& var
)时使用双“&”号。 html
首先,这只野兽叫什么? 我但愿Google容许咱们搜索这样的标点符号。 express
这究竟是什么意思? 函数
乍一看,它彷佛是一个双重引用(例如C风格的双重指针T** var
),可是我很难考虑到这种状况。 this
T&&
一词在与类型推导一块儿使用 (例如用于完美转发)时,一般被称为转发参考 。 术语“通用引用”由Scott Meyers 在本文中提出 ,但后来被更改。 spa
那是由于它能够是r值或l值。 .net
例如: 指针
// template template<class T> foo(T&& t) { ... } // auto auto&& t = ...; // typedef typedef ... T; T&& t = ...; // decltype decltype(...)&& t = ...;
能够在如下答案中找到更多讨论: 通用引用的语法 code
右值引用是一种行为,其行为与普通引用X&类似,但有一些例外。 最重要的是,对于函数重载解析,左值更喜欢旧式左值引用,而右值更喜欢新的右值引用: orm
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&&)
那么什么是右值? 任何不是左值的东西。 左值是表示存储位置的表达式,它容许咱们经过&运算符获取该存储位置的地址。 htm
首先经过一个示例来了解右值能够完成的工做:
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 };
构造函数和赋值运算符已被带有右值引用的版本重载。 右值引用容许函数在编译时(经过重载解析)在条件“是否在左值或右值上调用我?”时分支。 这使咱们可以在上面建立更有效的构造函数和赋值运算符,从而移动资源而不是复制资源。
编译器在编译时自动分支(取决因而为左值仍是右值调用它),选择是否应调用move构造函数或move赋值运算符。
总结:右值引用容许移动语义(以及完美的转发,在下面的文章连接中讨论)。
一个容易理解的实用示例是类模板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是一篇出色的文章,其中包括不少很好的例子,对全部这些内容(包括rvalue如何实现完美的转发以及这意味着什么)进行了解释 。 这篇文章主要依靠他的文章。
简短的介绍是Stroutrup等人撰写的“ 右值引用简介” 。 人
它声明一个右值参考 (标准建议文档)。
这是右值引用的简介 。
这是Microsoft标准库开发人员之一对rvalue引用进行的精彩深刻研究。
注意: MSDN上的连接文章(“ Rvalue参考:VC10中的C ++ 0x功能,第2部分”)是对Rvalue引用的很是清晰的介绍,可是对有关Rvalue引用的声明在C ++ 11草案中曾经是正确的。标准,但对于最后一个不是正确的! 具体来讲,它说在各个点上右值引用能够绑定到左值,这曾经是真实的,可是已经被更改。(例如int x; int && rrx = x;再也不在GCC中编译)– drewbarbs 2014年7月13日在16:12
C ++ 03引用(在C ++ 11中如今称为左值引用)之间的最大区别在于,它能够像临时元素同样绑定到右值,而没必要使用const。 所以,此语法如今合法:
T&& r = T();
右值引用主要提供如下内容:
移动语义 。 如今能够定义移动构造函数和移动赋值运算符,该运算符采用右值引用而不是一般的const-左值引用。 移动的功能相似于副本,只是它没有义务保持源不变。 实际上,它一般会修改源,使其再也不拥有已移动的资源。 这对于消除无关的副本很是有用,尤为是在标准库实现中。
例如,复制构造函数可能以下所示:
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中,咱们能够重载move构造函数:
foo(foo&& other) { this->length = other.length; this->ptr = other.ptr; other.length = 0; other.ptr = nullptr; }
注意这里的最大区别:move构造函数其实是修改其参数。 这将有效地将临时文件“移动”到正在构造的对象中,从而消除了没必要要的复制。
move构造函数将用于临时对象和很是量左值引用,这些引用使用std::move
函数(仅执行转换)显式转换为右值引用。 如下代码均调用f1
和f2
的move构造函数:
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
的构造函数采用int
,该参数也不会绑定到文字5。 好吧,咱们能够改用A1 const&
,可是若是foo
经过非const引用接受构造函数参数怎么办? 为了实现真正的通用工厂功能,咱们必须在A1&
和A1 const&
上重载工厂。 若是factory采用1个参数类型,可能会很好,可是每一个其余参数类型都会将必需的重载设置乘以2。这很快就没法维护。
右值引用经过容许标准库定义能够正确转发左值/右值引用的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得以保留。 这意味着,若是使用rvalue调用factory,则使用rvalue调用T
的构造函数。 若是使用左值调用factory,则使用左值调用T
的构造函数。 改进的工厂功能之因此有效,是由于如下一条特殊规则:
当函数参数类型的形式为
T&&
,其中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;
float f = 0f; int&& i = f;
格式良好,由于float能够隐式转换为int; 该引用将是转换后的临时结果。 std::move
调用很是重要: foo&& r = foo(); foo f = std::move(r);
foo&& r = foo(); foo f = std::move(r);
它表示右值参考。 右值引用将仅绑定到临时对象,除非以其余方式明确生成。 它们用于使对象在某些状况下更加高效,并提供一种称为“完美转发”的功能,该功能大大简化了模板代码。
在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&&);
考虑这些构造函数背后的实现。 在第一种状况下,字符串必须执行复制以保留值语义,这涉及新的堆分配。 可是,在第二种状况下,咱们预先知道传递给咱们的构造函数的对象将当即被销毁,而且没必要保持不变。 在这种状况下,咱们能够有效地交换内部指针而根本不执行任何复制,这实际上要更有效率。 移动语义可使任何昂贵或禁止复制内部引用资源的类受益。 考虑一下std::unique_ptr
的状况-如今咱们的类能够区分临时对象和非临时对象,咱们可使移动语义正确运行,从而不能复制unique_ptr
而是能够移动它,这意味着std::unique_ptr
能够能够合法地存储在Standard容器中,进行排序等,而C ++ 03的std::auto_ptr
不能。
如今,咱们考虑右值引用的另外一种用法-完美转发。 考虑将引用绑定到引用的问题。
std::string s; std::string& ref = s; (std::string&)& anotherref = ref; // usually expressed via template
记不清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&&
考虑最简单的模板功能-最小和最大。 在C ++ 03中,您必须手动重载const和非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能够接受左值,右值,常量左值的任意组合。