什么是左值?什么是右值?
通俗来讲,可以出现在赋值语句左侧的,为左值;只能出现在赋值语句右侧的,为右值。
左值与右值的本质区别在于:左值能取地址,但右值不能。
本文主要通过三个场景 —— 与 移动构造、移动赋值、完美转发 有关,讲解右值引用在实际场景中的作用及其相关的知识。
补充: 右值通常也被形象的称为“将亡值”,代指赋值重载和拷贝构造过程中产生的临时对象。
为了观察现象,我们要用到“自己搭的轮子”:
namespace MyTest { class string { public: typedef char* iterator; iterator begin() { return _str; } iterator end() { return _str + _size; } string(const char* str = "") :_size(strlen(str)) , _capacity(_size) { // cout << "string(const char* str)" << endl; _str = new char[_capacity + 1]; strcpy(_str, str); } // s1.swap(s2) void swap(string& s) { ::swap(_str, s._str); ::swap(_size, s._size); ::swap(_capacity, s._capacity); } // 拷贝构造 -- 左值 string(const string& s) :_str(nullptr) { cout << "string(const string& s) -- 深拷贝" << endl; _str = new char[s._capacity + 1]; strcpy(_str, s._str); _size = s._size; _capacity = s._capacity; } // 拷贝赋值 string& operator=(const string& s) { cout << "string& operator=(const string& s) -- 拷贝赋值" << endl; string tmp(s); swap(tmp); return *this; } ~string() { delete[] _str; _str = nullptr; } void reserve(size_t n) { if (n > _capacity) { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } } string& operator+=(char ch) { push_back(ch); return *this; } void push_back(char ch) { if (_size >= _capacity) { size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2; reserve(newcapacity); } _str[_size] = ch; ++_size; _str[_size] = '\0'; } const char* c_str() const { return _str; } private: char* _str = nullptr; size_t _size = 0; size_t _capacity = 0; // 不包含最后做标识的\0 }; string to_string(int value) { bool flag = true; if (value < 0) { flag = false; value = 0 - value; } string str; while (value > 0) { int x = value % 10; value /= 10; str += ('0' + x); } if (flag == false) { str += '-'; } std::reverse(str.begin(), str.end()); return str; } }
1. 移动构造
1.1 原理讲解
int main() { MyTest::string s1 = MyTest::to_string(1234); return 0; }
要分别从 C++11 前后
与 编译器优化前后
四个维度讨论 MyTest::string s1 = MyTest::to_string(1234);
的发生过程。
观察上图的右下角:很明显,str
是左值,在没有 move 的情况下,为什么可以直接移动构造?
实际上,编译器已经做过优化了 —— 将传值返回函数的返回值隐式地 move 过了。
1.2 移动构造实现
// 移动构造 string(string&& s) { cout << "string(string&& s) -- 移动构造" << endl; swap(s); }
2. 移动赋值
2.1 原理
int main() { MyTest::string s1; s1 = MyTest::to_string(1234); return 0; }
2.2 移动赋值实现
// 移动赋值 string& operator=(string&& s) { cout << "string& operator=(string&& s) -- 移动赋值" << endl; swap(s); return *this; }
3. 完美转发
3.1 forward 及其用法
move(
...
) 表达式的结果为右值,但并不会改变...
本身的属性 。
forward<T>(x)
返回一个引用表达式,这个引用表达式的类型取决于 x
的原始类型和上下文。
若 x 为右值,该引用表达式具有右值属性;
若 x 为左值,该引用表达式具有左值属性。
- 右值的右值引用具有左值属性
void func(const int& x) { cout << "const int&" << endl; } void func(int&& x) { cout << "int&&" << endl; } template<class T> void fun(T&& x) { func(x);// 此处 x 为右值还是左值呢?不妨运行该段代码,试试看 } int main() { // int&& a = 10;// 10 为右值;a 为 10 的右值引用,因此具有左值属性 fun(10); return 0; }
做一处修改:
template<class T> void fun(T&& x) { func(forward<T>(x));// 再运行试试 }
3.2 模板中的 && 万能引用
请记住:万能引用 与 模板 紧密相关!
void func(int& x) { cout << "左值引用" << endl; } void func(const int& x) { cout << "const 左值引用" << endl; } void func(int&& x) { cout << "右值引用" << endl; } void func(const int&& x) { cout << "const 右值引用" << endl; } template<class T> void Universal_citation(T&& x) // 万能引用 { func(forward<T>(x)); } int main() { int a; Universal_citation(a); Universal_citation(move(a)); const int b = 10; // b 必须要初始化 Universal_citation(b); Universal_citation(move(b)); return 0; }
3.3 实际场景
再引入一个“自己造的轮子” —— list :
namespace MyTest { template<class T> struct ListNode { ListNode<T>* _next; ListNode<T>* _prev; T _data; ListNode(const T& x = T()) :_next(nullptr) , _prev(nullptr) , _data(x) {} }; template<class T, class Ref, class Ptr> struct __list_iterator { typedef ListNode<T> Node; typedef __list_iterator<T, Ref, Ptr> self; Node* _node; __list_iterator(Node* x) :_node(x) {} // ++it self& operator++() { _node = _node->_next; return *this; } // it++ self operator++(int) { self tmp(*this); _node = _node->_next; return tmp; } self& operator--() { _node = _node->_prev; return *this; } self operator--(int) { self tmp(*this); _node = _node->_prev; return tmp; } Ref operator*() { return _node->_data; } Ptr operator->() { return &_node->_data; } bool operator!=(const self& s) { return _node != s._node; } bool operator==(const self& s) { return _node == s._node; } }; template<class T> class list { typedef ListNode<T> Node; public: typedef __list_iterator<T, T&, T*> iterator; list() { _head = new Node(T()); _head->_prev = _head; _head->_next = _head; } iterator begin() { return _head->_next; } iterator end() { return _head; } void swap(list<T>& tmp) { std::swap(_head, tmp._head); } void push_back(const T& x) { insert(end(), x); } iterator insert(iterator pos, const T& x) { Node* cur = pos._node; Node* newNode = new Node(x); // prev newNode cur Node* prev = cur->_prev; prev->_next = newNode; newNode->_prev = prev; newNode->_next = cur; cur->_prev = newNode; return newNode; } private: Node* _head = nullptr; }; }
为 push_back()
加上右值版本:
template<class T> struct ListNode { ListNode(T&& x) :_next(nullptr) ,_prev(nullptr) ,_data(forward<T>(x))// 完美转发 {} }; template<class T> class list { void push_back(T&& x) { insert(end(), forward<T>(x));// 关键处,完美转发 } iterator insert(iterator pos, T&& x) { Node* cur = pos._node; Node* newNode = new Node(forward<T>(x));// 完美转发 // prev newNode cur Node* prev = cur->_prev; prev->_next = newNode; newNode->_prev = prev; newNode->_next = cur; cur->_prev = newNode; return newNode; } };
4. 针对 移动构造 和 移动赋值重载
- 如果自己没有实现 移动构造 或 移动赋值重载,且没有实现析构函数、拷贝构造、赋值重载的任意一个,编译器会自动生成默认的移动构造或移动赋值重载。
内置类型,完成浅拷贝;自定义类型,调用实现的移动构造(没实现移动构造,则调用拷贝构造)。
- 如果提供了移动构造或移动赋值,则编译器不会自动生成拷贝构造和拷贝赋值。