什么是左值?什么是右值?
通俗来讲,可以出现在赋值语句左侧的,为左值;只能出现在赋值语句右侧的,为右值。
左值与右值的本质区别在于:左值能取地址,但右值不能。
本文主要通过三个场景 —— 与 移动构造、移动赋值、完美转发 有关,讲解右值引用在实际场景中的作用及其相关的知识。
补充: 右值通常也被形象的称为“将亡值”,代指赋值重载和拷贝构造过程中产生的临时对象。
为了观察现象,我们要用到“自己搭的轮子”:
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){}// ++itself& 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 curNode* 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 curNode* prev = cur->_prev;prev->_next = newNode;newNode->_prev = prev;newNode->_next = cur;cur->_prev = newNode;return newNode;}
};
4. 针对 移动构造 和 移动赋值重载
- 如果自己没有实现 移动构造 或 移动赋值重载,且没有实现析构函数、拷贝构造、赋值重载的任意一个,编译器会自动生成默认的移动构造或移动赋值重载。
内置类型,完成浅拷贝;自定义类型,调用实现的移动构造(没实现移动构造,则调用拷贝构造)。
- 如果提供了移动构造或移动赋值,则编译器不会自动生成拷贝构造和拷贝赋值。