右值引用和移动语义

什么是左值?什么是右值?

通俗来讲,可以出现在赋值语句左侧的,为左值;只能出现在赋值语句右侧的,为右值。

左值与右值的本质区别在于:左值能取地址,但右值不能

本文主要通过三个场景 —— 与 移动构造、移动赋值、完美转发 有关,讲解右值引用在实际场景中的作用及其相关的知识。

补充: 右值通常也被形象的称为“将亡值”,代指赋值重载和拷贝构造过程中产生的临时对象

为了观察现象,我们要用到“自己搭的轮子”:

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. 针对 移动构造 和 移动赋值重载

  • 如果自己没有实现 移动构造移动赋值重载,且没有实现析构函数、拷贝构造、赋值重载的任意一个,编译器会自动生成默认的移动构造或移动赋值重载。

​ 内置类型,完成浅拷贝;自定义类型,调用实现的移动构造(没实现移动构造,则调用拷贝构造)。

  • 如果提供了移动构造或移动赋值,则编译器不会自动生成拷贝构造和拷贝赋值

相关推荐

  1. C++:引用 && 移动赋值std::move分析

    2024-06-14 10:56:01       40 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-14 10:56:01       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-14 10:56:01       101 阅读
  3. 在Django里面运行非项目文件

    2024-06-14 10:56:01       82 阅读
  4. Python语言-面向对象

    2024-06-14 10:56:01       91 阅读

热门阅读

  1. 2024.6.13 刷题总结

    2024-06-14 10:56:01       29 阅读
  2. MySql几十万条数据,同时新增或者修改

    2024-06-14 10:56:01       26 阅读
  3. ELasticSearch数据迁移方案-elasticdump

    2024-06-14 10:56:01       26 阅读
  4. 前端针对需要递增的固定数据

    2024-06-14 10:56:01       26 阅读
  5. 如果用户访问的是没有页面的路由跳转到404

    2024-06-14 10:56:01       22 阅读
  6. Spring源码学习-Resource

    2024-06-14 10:56:01       29 阅读
  7. 行列视(RCV)能解决哪些问题?

    2024-06-14 10:56:01       24 阅读
  8. easyExcel导入日期LocalDateTime等类型不匹配问题

    2024-06-14 10:56:01       30 阅读
  9. milvus的磁盘索引

    2024-06-14 10:56:01       30 阅读
  10. npm发布自己的插件包

    2024-06-14 10:56:01       29 阅读
  11. redis的分布式session和本地的session有啥区别

    2024-06-14 10:56:01       28 阅读
  12. postgresql中geometry类型数据迁移

    2024-06-14 10:56:01       25 阅读