C++11 -- 右值引用和移动语义

news2024/11/19 1:24:34

文章目录

  • 基本概念
    • 左值和左值引用
    • 右值和右值引用
  • 右值引用和移动语义的意义和使用场景
    • 左值引用与右值引用比较
    • 右值引用的特殊场景
    • 左值引用的短板
    • 右值引用和移动语义
  • 完美转发
    • 模板中的&&万能引用
    • 完美转发在传参过程中保留原生类型属性
    • 完美转发实际中的使用场景

基本概念

在传统的C++语法中就有引用的语法,而在C++11中新增了右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用.但是无论是左值引用还是右值引用,都是给对象取别名.

左值和左值引用

左值是一个表示数据的表达式(如变量名或者解引用的指针),我们可以获取它的地址 + 可以对它赋值.

左值既可以出现在赋值符号的左边也可以出现在赋值符号的右边,但是定义const修饰左值时,我们不能对它赋值,但是可以取它的地址.

左值引用就是给左值的引用,给左值取别名.


int main()
{
	//p,b,c,*p都是左值.
	int* p = new int(0);

	int b = 1;

	const int c = 2;

	//以下几个是对上面左值的引用;
	int*& rp = p;

	int& rb = b;

	const int& rc = c;
	
	int& pvalue = *p;

	return 0;
    
}

右值和右值引用

右值也是一个表示数据的表达式,如:字面常量(11,hello等) ,表达式的返回值,函数返回等.

右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边(右值不能被修改),右值不能取地址.

右值引用就是对右值的引用,给右值取别名.

int main()
{
	//常见的右值
	10; 
	x + y; 
	fmin(x + y);

	//右值引用
	int&& r1 = 10;

	double&& r2 = x + y;

	double&& r3 = fmin(x + y);
	
	return 0;
}

右值引用和移动语义的意义和使用场景

左值引用与右值引用比较

一:左值引用总结
( 1 ) : 左值引用只能引用左值,不能引用右值.

( 2 ): 但是const左值引用既可以引用左值,也可以引用右值.

int main()
{

    int a = 10;

    int& ral = a;               //引用左值

  //  int& ra2 = 10;             //不能引用右值

    //const修饰的左值引用既可以引用左值,也可以引用右值.
    const int& ra3 = 10;      

    const int& ra4 = a;

    return 0;
}

二: 右值引用总结
( 1 ): 右值引用只能引用右值,不能引用左值.

( 2 ): 但是右值引用可以引用move以后的左值.


int main()
{
    int&& r1 = 10;       //右值引用引用右值.

    int a = 10;           

//  int&& r2 = a;        //右值引用不能引用左值

    int&& r3 = move(a);  //右值引用可以引用move以后的左值.
    
    return 0;
}

右值引用的特殊场景

需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址.例如: 我们不能取字面量为10的地址,但是rr1引用后,不但可以对rr1取地址,还可以修改rr1, 此时rr1可以类比于左值,当然,如果不想rr1被修改,也可以const int&& rr1去引用.

int main()
{
	double x = 1.1, y = 2.2;
	 
	int&& rr1 = 10;                          //右值引用.

	const double&& rr2 = x + y;              

	cout << &rr1 << endl;                    //此时的rr1类似于左值,可以取地址,可以被修改.

	rr1 = 11;
	 
    // rr2 = 3.4;           //不能被修改.
}

左值引用的短板

我们先看看左值引用的作用:
在这里插入图片描述

那么,左值引用的盲区是什么?

例如1: 在to_string函数中,当我们使用引用返回时:
在这里插入图片描述
例如二: 再比如我们写得 杨辉三角 中:
在这里插入图片描述
针对以上情况: 在C++11以前,我们只能额外增加一个输出类型的参数,将别人需要改变的类型变量用引用接收,然后经过函数一系列的操作后返回所需要的类型,但是这样不好的缺点,就是不符合使用习惯.
在这里插入图片描述
所以,C++11中的右值引用一个重要功能就是要解决上面的问题.

右值引用和移动语义

为了便于以下测试展示出更好的效果,下面是我们以前模拟出来的string类,里面包含了一些最基本的成员函数,以及在拷贝构造和复制重载中新增了表示调用过该函数的提示语.并且新增了一个to_string函数.

namespace yzh
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str; 
		}
		iterator end()
		{
			return _str + _size; 
		}
		//构造函数
		string(const char* str = "")
		{
			_size = strlen(str); 
			_capacity = _size; 
			_str = new char[_capacity + 1]; 
			strcpy(_str, str); 
		}
		//交换两个对象的数据
		void swap(string& s)
		{
			//调用库里的swap
			::swap(_str, s._str); 
			::swap(_size, s._size); 
			::swap(_capacity, s._capacity);
		}
		//拷贝构造函数(现代写法)
		string(const string& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;

			string tmp(s._str); 
			swap(tmp); 
		}
		//赋值运算符重载(现代写法)
		string& operator=(const string& s)
		{
			cout << "string& operator=(const string& s) -- 深拷贝" << endl;

			string tmp(s); 
			swap(tmp); 
			return *this; 
		}
		//析构函数
		~string()
		{
			delete[] _str; 
			_str = nullptr;
			_size = 0;      
			_capacity = 0;  
		}
		//[]运算符重载
		char& operator[](size_t i)
		{
			assert(i < _size); 
			return _str[i]; 
		}
		//改变容量,大小不变
		void reserve(size_t n)
		{
			if (n > _capacity) 
			{
				char* tmp = new char[n + 1]; 
				strncpy(tmp, _str, _size + 1); 
				delete[] _str; 
				_str = tmp; 
				_capacity = n; 
			}
		}
		//尾插字符
		void push_back(char ch)
		{
			if (_size == _capacity) 
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2); 
			}
			_str[_size] = ch; 
			_str[_size + 1] = '\0'; 
			_size++; 
		}
		//+=运算符重载
		string& operator+=(char ch)
		{
			push_back(ch); 
			return *this;
		}
		//返回C类型的字符串
		const char* c_str()const
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
	    //to_sting函数
		yzh::string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}
		yzh::string str;
		while (value > 0)
		{
			int x = value % 10;
			value /= 10;
			str += (x + '0');
		}
		if (flag == false)
		{
			str += '-';
		}
		std::reverse(str.begin(), str.end());
		return str;
	}
}


以下为当我们使用to_string函数分别为编译器优化前和优化后调用拷贝次数示图:
在这里插入图片描述

虽然在以前时,拷贝构造(形参为const修饰) 既可以修饰左值,又可以修饰右值,但随着移动构造和移动赋值的提出,编译器会优先选择调用与函数形参最匹配的成员函数.
当实参为左值时,编译器默认调用拷贝构造.
当实参为右值时,编译器默认调用移动构造.

//移动构造
		string(string&& s)
			:str(nullptr)
		{
			cout << "string( const string&& s ) ----资源转移" << endl;
			swap(s);
		}

测试如下:
如果没有移动构造,即使在编译器优化后也调用了一次拷贝构造.
如果调用移动构造,那么对于编译器来说优化后就减少了一次拷贝构造.
在这里插入图片描述
以下为编译器优化前和编译器优化后调用移动构造图示:
在这里插入图片描述

完美转发

模板中的&&万能引用

在C++11中模板中的&&并不代表右值引用,而是万能引用,其节能接收左值又能接收右值.

template<class T>
void PerfectForward(T&& t)
{
	//...
}

测试代码如下:

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 PerfectForward(T&& t)
{
	Func(t);
}
int main()
{
	int a = 10;
	PerfectForward(a);       //左值
	PerfectForward(move(a)); //右值

	const int b = 20;
	PerfectForward(b);       //const 左值
	PerfectForward(move(b)); //const 右值

	return 0;
}

运行结果如下;
我们知道,模板的万能引用只是提供了能够同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,不管我们传递的实参是左值还是右值,在后续的使用中都退化成了左值.
在这里插入图片描述

完美转发在传参过程中保留原生类型属性

完美转发对实参会改变实参的属性,那么我们希望在传递的过程中保持实参的左值或者右值的属性,就需要我们下面学习的完美转发了.

std::forward完美转发在传参过程中保留对象原生类型属性.

为了达到该效果,我们只要在传参时调用forward模板函数就可以了.

template<class T>
void PerfectForward(T&& t)
{
	Func(std::forward<T>(t));
}

代码效果如下:
我们知道,经过完美转发之后:
在传参时传入左值,编译器就会匹配到形参为左值的Fun函数.
在传参时传入右值,编译器就会匹配到形参为右值类型的Func函数.
在这里插入图片描述

完美转发实际中的使用场景

下面是我们曾经实现的list代码,在lsit类当中分别新增提供左值引用的和右值引用的push_back,insert函数.

namespace yzh
{
	template<class T>
	struct list_node
	{
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;

		list_node(const T& x = T())
			:_data(x)
			, _next(nullptr)
			, _prev(nullptr)
		{}

		list_node(T&& x)
			:_data(std::forward<T>(x))
			, _next(nullptr)
			, _prev(nullptr)
		{}
	};

	// typedef __list_iterator<T, T&, T*>             iterator;
	// typedef __list_iterator<T, const T&, const T*> const_iterator;

	// 像指针一样的对象
	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef list_node<T> Node;
		typedef __list_iterator<T, Ref, Ptr> iterator;

		typedef bidirectional_iterator_tag iterator_category;
		typedef T value_type;
		typedef Ptr pointer;
		typedef Ref reference;
		typedef ptrdiff_t difference_type;


		Node* _node;

		// 休息到17:02继续
		__list_iterator(Node* node)
			:_node(node)
		{}

		bool operator!=(const iterator& it) const
		{
			return _node != it._node;
		}

		bool operator==(const iterator& it) const
		{
			return _node == it._node;
		}

		// *it  it.operator*()
		// const T& operator*()
		// T& operator*()
		Ref operator*()
		{
			return _node->_data;
		}

		//T* operator->() 
		Ptr operator->()
		{
			return &(operator*());
		}

		// ++it
		iterator& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		// it++
		iterator operator++(int)
		{
			iterator tmp(*this);
			_node = _node->_next;
			return tmp;
		}

		// --it
		iterator& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		// it--
		iterator operator--(int)
		{
			iterator tmp(*this);
			_node = _node->_prev;
			return tmp;
		}
	};

	template<class T>
	class list
	{
		typedef list_node<T> Node;
	public:
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;

		//typedef __reverse_iterator<iterator, T&, T*> reverse_iterator;
		//typedef __reverse_iterator<const_iterator, const T&, const T*> //const_reverse_iterator;


		const_iterator begin() const
		{
			return const_iterator(_head->_next);
		}

		const_iterator end() const
		{
			return const_iterator(_head);
		}

		iterator begin()
		{
			return iterator(_head->_next);
		}

		iterator end()
		{
			return iterator(_head);
		}

		/*reverse_iterator rbegin()
		{
			return reverse_iterator(end());
		}

		reverse_iterator rend()
		{
			return reverse_iterator(begin());
		}*/

		void empty_init()
		{
			// 创建并初始化哨兵位头结点
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}

		template <class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			empty_init();

			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		list()
		{
			empty_init();
		}

		void swap(list<T>& x)
			//void swap(list& x)
		{
			std::swap(_head, x._head);
		}

		// lt2(lt1)
		list(const list<T>& lt)
		{
			empty_init();
			list<T> tmp(lt.begin(), lt.end());
			swap(tmp);
		}

		// lt1 = lt3
		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}

		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}

		void push_back(const T& x)
		{
			//Node* tail = _head->_prev;
			//Node* newnode = new Node(x);

			 _head          tail  newnode
			//tail->_next = newnode;
			//newnode->_prev = tail;
			//newnode->_next = _head;
			//_head->_prev = newnode;

			insert(end(), x);
		}

		void push_back(T&& x)
		{
			insert(end(), std::forward<T>(x));
		}

		void push_front(const T& x)
		{
			insert(begin(), x);
		}

		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;

			Node* newnode = new Node(x);

			// prev newnode cur			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			return iterator(newnode);
		}

		iterator insert(iterator pos, T&& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;

			Node* newnode = new Node(std::forward<T>(x));

			// prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			return iterator(newnode);
		}

		void pop_back()
		{
			erase(--end());
		}

		void pop_front()
		{
			erase(begin());
		}

		iterator erase(iterator pos)
		{
			assert(pos != end());

			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

			prev->_next = next;
			next->_prev = prev;
			delete cur;

			return iterator(next);
		}

	private:
		Node* _head;
	};
}

在list类型存储的数据类型就为我们模拟实现的string类,此时,我们分别调用list中的push_back.

int main()
{
	yzh::list<yzh::string> lt;

	yzh::string s("1111");

  	lt.push_back(s);      //调用左值引用版本的push_back

	lt.push_back("2222"); //调用右值引用版本的push_back
	return 0;
}

图示相关解释如下:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/517138.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何用ChatGPT分析品牌舆论传播概况,并给到处理建议?

该场景对应的关键词库&#xff08;25个&#xff09;&#xff1a; 舆论传播、数据分析、主题、事件、时间段、媒体渠道、数据来源、情感分析、关键词提取、主题挖掘、大众集中讨论的话题、讨论关注程度、舆论关注倾向、关联类似事件、聚焦某一种情绪、人群范围、事件涉及群体、谁…

企业电子招标采购系统源码java 版本 Spring Cloud + Spring Boot

项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及…

程序员的职场,光有技术是不行的,送给每个即将工作的程序员

又是一年五月份&#xff0c;大批量学计算机的学生又要涌入职场了&#xff0c;牛皮的已经早早找到了工作&#xff0c;但不管你技术再牛&#xff0c;在程序员的职场&#xff0c;光有技术是不行的&#xff0c;你还要懂得一些职场的雷坑和上升技巧。 我做了二十多年程序员&#xf…

Stable diffusion教程 - 提示词汉化

1. 介绍 安装stable diffusion后&#xff0c;可能英语不熟悉&#xff0c;可能提示词不熟悉&#xff0c;写提示词就比较困难。 这款提示词汉化插件&#xff0c;配合中文词库输入中文就能提示相关提示词&#xff0c;用起来超级方便&#xff0c;示例如下&#xff1a; 输入“花”…

windows下搭建局域网的mysql.md

场景&#xff1a;公司的需要搭建一个局域网的mysql服务器&#xff0c;利用phpstudy傻瓜式安装后&#xff0c;用本机访问&#xff0c;提示 1130 - Host ‘DESKTOP-IRSGN4A’ is not allowed to connect to this MySQL server mysql 安装软件 1、命令行进入mysql 首先我们需要进…

Python基础入门编程代码练习(六)

一、模拟房产经纪人来管理房屋信息 编写业务实现 家具类&#xff1a;HouseItem 属性&#xff1a;名字 name&#xff0c;占地面积 area 方法&#xff1a;__init__ , __str__ 类名&#xff1a;房子类 House 属性&#xff1a;户型 name&#xff0c;总面积&#xff1a;total_are…

为什么编程都建议不要用拼音命名

一、场景 我们看看知乎答主举的搞笑例子&#xff0c;一句话全部都是shi&#xff0c;表达起来确实困难。 二、原因 上面这个回答&#xff0c;一句话全部都是“shi”&#xff0c;表达起来确实困难。并且让人误解 那么编程都建议不要用拼音命名&#xff0c;主要有以下原因&…

数据结构_栈、队列和数组

目录 1. 栈 1.1 栈的定义 1.2 栈的基本操作 1.3 栈的顺序存储结构 1.3.1 顺序栈 1.3.2 顺序栈的基本运算 1.3.3 共享栈 1.4 栈的链式存储 1.5 栈相关应用 2. 队列 2.1 队列的定义 2.2 队列的基本操作 2.3 队列的顺序存储 2.4 循环队列 2.4.1 循环队列的操作 2.…

2023年全国职业院校技能大赛-大数据应用开发-数据可视化

可视化题目与以往相同&#xff0c;做法类似&#xff0c;我这里展示得到语句后处理优化以后的代码&#xff0c;以函数式来写可视化&#xff0c;比以前400-500多行代码简洁到100多行。其他题目见本栏目&#xff0c;那里面的代码都是没有优化后的&#xff0c;这次主要以效率和精简…

提升曝光率!掌握Facebook帖子关键词采集技巧

如何提高Facebook帖子的曝光率成为了每个营销人员的关注焦点。掌握Facebook帖子关键词采集技巧&#xff0c;可以帮助你更好地定位受众&#xff0c;增加帖子的曝光和点击率。在本文中&#xff0c;我们将详细介绍一些有效的技巧和策略&#xff0c;让你成为Facebook帖子关键词的专…

不到1分钟,帮你剪完旅行vlog,火山引擎全新 AI「神器」真的这么绝?

旅行时&#xff0c;想在社交平台发布一支精美的旅行 vlog&#xff0c;拍摄剪辑需要花费多长时间&#xff1f; 20 分钟&#xff1f;一小时&#xff1f;半天&#xff1f; 在火山引擎算法工程师眼里&#xff0c;可能 1 分钟都用不了&#xff0c;因为会有 AI 替你完成。 没错&#…

安装-唯一客服系统文档中心

环境要求 Mysql > 5.6IIS/Apache/Nginx(只推荐nginx) 宝塔一键部署 前往 【软件商店】>【一键部署】>【导入项目】 客服项目本身不需要PHP环境&#xff0c;因此PHP版本那里&#xff0c;随意根据自己环境写上就可以 导入完成以后&#xff0c;点击一键部署&#xff0c;填…

matlab实验四插值与数据拟合

一、实验目的及要求 一、实验的目的与要求&#xff1a; 1、掌握 MATLAB的一维数据插值法 2、通过比较不同次数的多项式拟合效果&#xff0c;了解多项式拟合的原理 3、掌握 MATLAB的多项式拟合的特点和方法 4、掌握 MATLAB的多项式表示与运算 二、实验原理 1、Matlab中&#xff…

基于QEMU的RISC-V架构linux系统开发(一)——RISC-V交叉编译器的安装

基于RISC-V交叉编译器包括32bit和64bit两种类型&#xff0c;其中每种类型又包括裸机版本&#xff08;newlib&#xff09;和动态链接库版本&#xff08;linux glibc&#xff09;。不同类型、版本的gcc的安装仅在配置文件上存在差异&#xff0c;具体安装流程&#xff08;以64bit …

以数据思维和技能提升数据应用测试实践 | 京东云技术团队

作者&#xff1a;京东零售 周雪梅 以数据思维和技能提高测试覆盖率和效率。数据应用测试&#xff0c;功能测试主要聚焦在数据流向&#xff08;输入和输出&#xff09;。 一、背景 数据质量组当前主要承接黄金眼和商智中的供应链模块&#xff0c;商智包括PC&#xff08;品牌版…

Access、Foxpro、Foxbase,2023年找到完美代替,有Excel基础即可

你还记得上世纪80年代的Foxbase和Foxpro吗&#xff1f; 数据库软件作为基础软件是计算机系统稳定运行的基石。 像Foxbase和Foxpro&#xff0c;很多计算机专业的前辈都应该听说过&#xff0c;当时的风靡程度一点也不亚于现在的微软office。 FoxPro和Foxbase是诞生于1984年。具…

玩机搞机-----安卓全机型 ADB FAST 各种指令解析说明与操作【二】基础联机

安卓全机型 玩机 搞机 ADB FAST 各种指令解析说明与操作_adb线刷命令_安卓机器的博客-CSDN博客 今天对上个帖子不足的地方进行补正。方便友友进行基础的联机操作&#xff0c;很多时候我们用adb指令的时候会有各种奇奇怪怪的问题。例如同一个机型&#xff0c;同一个指令。有时候…

使用svg在元素直接绘制连线箭头

注意&#xff1a;svg的图形绘制的点位置坐标是基于画布的位置坐标&#xff0c;相当于从左上角的点为起点。 先来个简单示例&#xff1a; 在点与点之间绘制连线箭头 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8">…

cookie、session、token的区别是什么

前言 今天就来说说session、cookie、token这三者之间的关系&#xff01;最近这仨玩意搞得头有点大&#x1f923; 1.为什么会有它们三个&#xff1f; 我们都知道 HTTP 协议是无状态的&#xff0c;所谓的无状态就是客户端每次想要与服务端通信&#xff0c;都必须重新与服务端链接…

JUC并发编程18 | AQS分析

尚硅谷&#xff08;140-155&#xff09; 18 AQS 前置知识 公平锁和非公平锁可重入锁自旋思想LockSupport双向链表设计模式——模块设计 18.1 AQS入门级别理论知识 AQS一般指的是 AbstractQueuedSynchronized AQS 是用来实现锁或者其他同步器组件的公共基础部分的抽象实现…