右值引用
左值和右值
(1)两者区别:
①左值:能对表达式取地址、或具名对象/变量。一般指表达式结束后依然存在的持久对象。
②右值:不能对表达式取地址,或匿名对象。一般指表达式结束就不再存在的临时对象。
总结:一般而言,一个左值表达式表示的是一个对象的身份,而一个右值表达式表示的是对象的值。
(2)右值的分类
①将亡值(xvalue,eXpiring value):指生命期即将结束的值,一般是跟右值引用相关的表达式,这样表达式通常是将要被移动的对象,如返回类型为T&&的函数返回值(如std::move)、经类型转换为右值引用的对象(如static_cast<T&&>(obj))、xvalue类对象的成员访问表达式也是一个xvalue(如Test().memberdata,注意Test()是个临时对象)
②纯右值(prvalue, PureRvalue):按值返回的临时对象、运算表达式产生的临时变对象、原始字面量和lambda表达式等。
(3)C++11中的表达式
标准库的 move 函数
• 虽然不能将一个右值直接绑定到一个左值上,但可以显式地将一个左值转换为对应的右值引用类型。我们可以通过调用一个名为 move的新标准库函数来获得绑定到左值上的引用。头文件utility。
int &&rr3 = std::move(rr1); //ok
• 我们可以销毁一个移后源对象,也可以赋予它新值,但不能使用一个移后源对象的值。
移动构造函数和移动赋值运算符
• 与拷贝函数不同,移动构函数不分配任何新的内存,它接管给定StrVec中的内存,在接管内存之后, 它将给的对象中的指针置为nullptr,这样就完成了从给定对象的移动操作,此对象将继续存在。

1 #include<iostream> 2 #include<string> 3 #include<memory> 4 using namespace std; 5 6 class StrVec { 7 public: 8 StrVec(): elements(nullptr), first_free(nullptr), cap(nullptr) {} 9 StrVec(const StrVec &); 10 StrVec& operator=(const StrVec&); 11 ~StrVec() { free(); }; 12 13 StrVec(StrVec &&s) noexcept; 14 StrVec& opearator=(StrVec &&rhs) noexcept; 15 16 void push_back (const string&); 17 size_t size() const { return first_free - elements; } 18 size_t capacity() const { return cap - elements; } 19 string *begin() const { return elements; } 20 string *end() const { return first_free; } 21 22 private: 23 static allocator<string> alloc; 24 void chk_n_alloc() { if (size() == capacity()) reallocate(); } 25 pair<string*, string*> alloc_n_copy(const string*, const string *); 26 void free(); 27 void reallocate(); 28 string *elements; //指向数组首元素的指针 29 string *first_free; //指向数组第一个空闲元素的指针 30 string *cap; //指向数组尾后位置的指针 31 }; 32 33 StrVec::StrVec(const StrVec &s) 34 { 35 auto newdata = alloc_n_copy(s.begin(), s.end()); 36 elements = newdata.first; 37 first_free = cap = newdata.second; 38 } 39 40 StrVec& StrVec::operator=(const StrVec &rhs) 41 { 42 auto data = alloc_n_copy(rhs.begin(), rhs.end()); 43 free(); 44 elements = data.first; 45 first_free = cap = newdata.second; 46 return *this; 47 } 48 49 void StrVec::free() 50 { 51 if (elements) 52 { 53 for (auto p = first_free; p != elements; ) 54 alloc.destroy(--p); 55 alloc.deallocate(elements, cap - elements); 56 } 57 } 58 59 pair<string *, string *> StrVec::alloc_n_copy(const string *b, const string *e) 60 { 61 auto data = alloc.allocate(e - b); 62 return { data, uninitialized_copy(b, e, data) }; 63 } 64 65 void StrVec::push_back(const string& s) 66 { 67 chk_n_alloc(); 68 alloc.construct(first_free++, s); 69 } 70 71 void StrVec::reallocate() 72 { 73 auto newcapacity = size() ? 2 * size() : 1; 74 auto newdata = alloc.allocate(newcapacity); 75 auto dest = newdata; 76 auto elem = elements; //原对象的elements指针 77 for (size_t i = 0; i != size(); ++i) 78 alloc.construct(dest++, std::move(*elem++)); 79 free(); 80 elements = newdata; 81 first_free = dest; 82 cap = elements + newcapacity; 83 } 84 85 StrVec::StrVec(StrVec &&s) noexcept 86 : elements(s.elements), first_free(s.first_free), cap(s.cap) 87 { 88 s.elements = s.first_free = cap = nulllptr; 89 } 90 91 StrVec& StrVec::StrVec(StrVec &&s) noexcept 92 { 93 if (this = &s) 94 { 95 free(); 96 elemens = rhs.elements; 97 first_free = .frhsirst_free; 98 cap = rhs.cap; 99 rhs.elements = srhs.first_free = rhs.cap = nullptr; 100 } 101 return *this; 102 }
• 与拷贝操作不同,编译器根本不会为某些类合成移动操作,特别是,如果一个类定义了自己的拷贝构造函数、拷贝赋值运算符或析构函数、编译器根本就不会为它合成移动构造函数。
• 如果一个类有一个可用的拷贝构造函数而没有移动构造函数,则其对象是通过拷贝构造函数来“移动”的,拷贝赋值运算符和移动赋值运算符的情况类似。
拷贝并交换赋值运算符和移动操作
右值引用和成员函数
• 区分移动和拷贝的重载函数通常有一个版本接受一个const T&, 而另一个版本接受一个T&&。
作为一个具体例子,我们将为StrVec类定义另一个版本的push_back:
1 class StrVec { 2 public: 3 void push_back(const std::string&); //拷贝元素 4 void push_back(std:: string&&) //移动元素 5 // 其他成员的定义, 如前 6 }; 7 8 void StrVec::push_back(const string &s) 9 { 10 chk_n_alloc(); //确保有空间容纳新的元素 11 //在first_free指向的元素中构造s的一个副本 12 alloc.construct(first_free++, s); 13 } 14 15 void StrVec::push_back(string &&s) 16 { 17 chk_n_alloc(); // 如果需要的话为StrVec重新分配内存 18 alloc.construct(first_free++, std::move(s)); 19 }
右值和左值引用成员函数
1 #include <iostream> 2 using namespace std; 3 4 class Foo { 5 public: 6 Foo & operator=(const Foo&) &; //只能向可修改的左值赋值 7 Foo anotherMem() const &; //正确,const 限定符在前 8 9 Foo gg() const &&; //this 也可以指向一个右值 10 }; 11 12 Foo& Foo::operator=(const Foo &rhs) & //引用限定符和const一样必须同时出现在声明和定义中 13 { 14 //执行将rhs赋予本对象所需的工作 15 return *this; 16 } 17 18 Foo& retFoo() //返回一个左值 19 { 20 return *(new Foo()); 21 } 22 23 Foo retVal() //返回一个右值 24 { 25 return Foo(); 26 } 27 28 int main(void) 29 { 30 Foo i, j; 31 i = j; 32 retFoo() = j; //正确,retFoo() 返回一个左值 33 i = retVal(); //正确,我们可以将一个右值作为赋值运算符右侧运算对象 34 return 0; 35 }
重载和引用函数
1 #include <iostream> 2 #include <vector> 3 #include <algorithm> 4 using namespace std; 5 6 class Foo{ 7 public: 8 Foo sorted() &&; //可用于改变的右值 9 Foo sorted() const &; //可用于任何类型的Foo 10 // Foo sorted(); //错误,当有两个以上同名且同参数列表的成员函数时, 11 12 // 要么全(指这些同名且同参数列表的成员函数)都加引用限定符(只要有就行, 不论右值还是左值引用),要么都不加引用限定符 13 Foo sorted(int);//正确,参数列表不同 14 15 private: 16 vector<int> data; 17 }; 18 19 Foo Foo::sorted() && // 本对象为右值,因此可以原址排序 20 { 21 sort(data.begin(), data.end()); 22 return *this; 23 } 24 25 Foo Foo::sorted() const & // 本对象是const或是一个左值,哪种情况我们都不能原址排序 26 27 { 28 Foo ret(*this); // 拷贝一个副本 29 sort(ret.data.begin(), ret.data.end()); // 副本排序 30 return ret; // 返回副本 31 } 32 33 Foo& retFoo() //返回一个左值 34 { 35 return *(new Foo()); 36 } 37 38 Foo retVal() //返回一个右值 39 { 40 return Foo(); 41 } 42 43 int main(void) 44 { 45 retVal().sorted(); //retVal() 返回一个右值,调用 Foo::sorted() && 46 retFoo().sorted(); //retFoo() 返回一个左值,调用 Foo::sorted() const & 47 return 0; 48 }
参考资料
【1】右值引用(1)_基本概念