C++11: 右值引用,移动语义,万能引用,完美转发,新的默认成员函数

news2024/11/16 8:49:49

C++11: 右值引用,移动语义,万能引用,完美转发,新的默认成员函数

  • 一.左值和右值
    • 1.左值
    • 2.右值
    • 3.左值,右值和能否被修改的关系
  • 二.左值引用的好处和局限
    • 1.完全解决了传值传参时的深拷贝问题
    • 2.传引用返回时需要注意的点
      • 1.坑点:传引用返回用值接收
      • 2.传引用返回用引用接收
      • 3.应该怎么办?
      • 4.如果传值返回拿引用接收呢?
        • 1.左值引用与右值的关系
    • 3.传值返回时的深拷贝问题
      • 1.编译器未优化的情况
      • 2.编译器优化的情况
  • 三.右值引用与移动语义
    • 1.为什么要有右值引用?
    • 2.移动构造
      • 1.移动构造的价值所在
      • 2.纯右值和将亡值
      • 3.为什么const左值引用能够引用右值
    • 3.移动赋值
    • 4.右值引用与左值的关系
  • 四.STL库的变化
  • 五.改造list的insert,使其支持右值引用类型
    • 1.补充
      • 1.为什么右值引用本身是左值呢?
      • 2.const右值引用的价值是什么
      • 3.右值引用的底层与右值引用的修改
    • 2.改造
    • 3.验证
  • 六.右值引用使用时的注意事项
  • 七.万能引用
  • 八.完美转发
  • 九.新的默认成员函数

C++11引入了右值引用的语法之后
许多大佬就利用右值引用设计出了移动语义,而随着移动语义的出现与普及,为了方便移动语义的使用,随之产生了完美转发这一语法

因此不要把右值引用和移动语义混为一谈
移动语义是右值引用的一大重要应用,而不是右值引用本身

一.左值和右值

在C语言当中,左值和右值这两个概念就早已存在
要真正了解右值引用,就必须要先了解左值和右值

1.左值

  1. 左值并不是单纯指的可以被修改的变量/表达式,而是可以被取地址的变量/表达式
  2. 只要一个变量/表达式能被取地址,该变量/表达式就是左值
  3. 左值既可以出现在赋值符号的左边,也可以出现在赋值符号的右边
  4. const修饰的左值,不能被修改,但是可以取地址
  5. 我们之前所学的引用都是左值引用,符号为&
int main()
{
	int a = 1;
	int* p = new int(1);
	cout << &a << endl;
	cout << &p << endl;
	cout << &*p << " " << p << endl;//p就是*p取地址 p==&*p

	//a,*p,p都是可以被修改的左值
	a = 10;
	*p = 10;
	p = nullptr;

	const int c = 1;
	cout << &c << endl;//c可以被取地址,因此c是左值,只不过因为c被const所修饰,因此c不可被修改
	//c = 10;//c是不可被修改的左值
	return 0;
}

2.右值

  1. 右值是不能被取地址的值
  2. 包括:字面常量0,1,2,3… 函数返回值(返回值类型不是左值引用时) 匿名对象(因为匿名对象的生命周期只有一行,而且没有名字,也就没有地址)等等
  3. 右值不能出现在赋值符号左边,只能出现在赋值符号的右边
  4. 右值不能被修改
  5. 我们今天所学的右值引用的符号为&&
    在这里插入图片描述

3.左值,右值和能否被修改的关系

在这里插入图片描述

二.左值引用的好处和局限

如果大家对左值引用不是很熟悉的话,可以看一下我的这篇文章:
C++入门-引用
在了解了左值和右值之后,我们来回顾一下左值引用的好处和局限
然后就能理解为什么C++11要引入右值引用这一语法
在这里插入图片描述

1.完全解决了传值传参时的深拷贝问题

class A
{
public:
	A()
	{
		_v.resize(10000);
	}
	A(const A& a)
	{
		cout << "A(const A& a)" << endl;
	}
private:
	vector<int> _v;
};

void func1(A a)
{
	cout << "void func1(A a)" << endl;
}

void func2(A& a)
{
	cout << "void func1(A a)" << endl;
}

int main()
{
	A a;
	func1(a);
	cout << "================================" << endl;
	func2(a);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
有了左值引用作为函数的参数
如果你不需要在函数内修改该形参,那么建议传引用作为参数
如果你需要在函数内修改该形参,且想要保证实参不被改变,那么老老实实传值吧
因为这样的需求使得你无论如何都需要拷贝一份

2.传引用返回时需要注意的点

需要注意的是:
传引用返回时必须保证返回值在出了函数作用域之后生命周期还没有结束才可以
否则就是坑人
我们先来看一下坑人的点

1.坑点:传引用返回用值接收

在这里插入图片描述
调试走一波
在这里插入图片描述

2.传引用返回用引用接收

如果说上面的行为在编程界是违反行为,那下面的就是妥妥的犯罪行为了,因为这个进程的肆意妄为,导致OS都要介入了
在这里插入图片描述

3.应该怎么办?

只有当你这个局部变量除了作用域还在,此时可以传引用返回,用引用接收或者用值接收(看你的需求)

比如说静态变量/全局变量的生命周期是全局的,一直存在到程序运行结束
或者是堆上的数据也可以
在这里插入图片描述
因此引用作为返回值时只能解决一部分问题而已,
对于局部非静态变量来说,不能使用传引用返回,否则轻则违法,重则犯罪(语法的法)

4.如果传值返回拿引用接收呢?

我们发现了2个现象

1.左值引用与右值的关系

1.左值引用一般来说不能引用右值
证明:
在这里插入图片描述
2.const左值引用可以引用右值
在这里插入图片描述

3.传值返回时的深拷贝问题

1.编译器未优化的情况

在这里插入图片描述

2.编译器优化的情况

在这里插入图片描述
在这里插入图片描述
这是编译器优化的情况,这里用的VS2019
在这里插入图片描述
我们可以看出
早在C++11之前,编译器就进行了优化,但是还不够,因为语法的限制,有些事情他想做但是做不到

三.右值引用与移动语义

1.为什么要有右值引用?

为了解决局部静态变量无法传引用返回,而只能传值返回造成的资源浪费问题,编译器做了优化,将2次拷贝构造优化为了1次拷贝构造

但是因为C++是追求效率的语言,所以很多大佬就在想能不能让语法开个绿灯,让我们能够将刚才的tmp的资源转移给ret1呢?

反正tmp出了作用域就销毁了,那些资源他也不用,我们帮他用了,这不就是提高资源的利用率吗,而且还避免了浪费,多好啊

正因如此,C++11语法就引入了右值引用,开了绿灯

这个绿灯如何开的呢?
右值引用到底有什么神奇之处呢?
我们以我们之前写过的string类来作为示例一起探索一下右值引用的神奇之处和大佬们的天才设计

源码在这篇博客当中,我就不cv过来了,因为太长了
C++: string的模拟实现
为了方便演示,拷贝构造和赋值运算符重载我们不用现代写法,用传统写法

这是右值引用出现之前,在编译器优化之下的拷贝情况
在这里插入图片描述

2.移动构造

右值引用出现之后,所有的传值返回(我们只谈自定义类型)的返回值(我们姑且统一叫作tmp)
都会被编译器识别为右值进行处理

此时为了让tmp的资源成功转移到接收值(我们姑且统一叫作ret)当中,大佬们根据右值引用和编译器的特殊识别处理设计出了移动构造(是拷贝构造的一种重载形式)
上代码:

string::string(string&& s)
{
	cout << "string::string(string&& s) 移动构造" << endl;
	swap(s);
}

在这里插入图片描述
下面我们调试走一波
在这里插入图片描述
在这里插入图片描述
因此,移动构造的出现是C++委员会,编译器,库的编写者三者齐心协力完成的

右值引用的出现使得右值可以被起别名,同时编译器对传值返回的tmp进行特殊识别处理,将其识别为右值,
此时库的编写者就对拷贝构造写了一个重载版本,专门将右值的资源转移给要构造的对象,从而将拷贝次数减为了0

此时就完全解决了传值返回时的深拷贝问题
不过还有几点我们也要仔细介绍

1.移动构造的价值所在

在这里插入图片描述

2.纯右值和将亡值

在这里插入图片描述

3.为什么const左值引用能够引用右值

在这里插入图片描述

3.移动赋值

可是如果我们把它写成2行呢?
此时就不是构造了,而是赋值了,因此情况就发生了变化
在这里插入图片描述
在这里插入图片描述
此时发生了一次移动构造和一次拷贝赋值重载
刚才刚把那个拷贝优化掉,现在又来了一个拷贝
怎么办?照猫画虎再写一个移动赋值就ok了

string& string::operator=(string&& s)
{
	if (this != &s)
	{
		cout << "string& string::operator=(string&& s) 移动赋值" << endl;
		swap(s);
	}
	return *this;
}

在这里插入图片描述
搞定

4.右值引用与左值的关系

右值引用不能引用左值,但是可以引用move(左值)
move(左值)的返回值类型是一个右值
而move(左值)之后,该左值依然是左值,并不会变成右值

注意:

  1. move(左值)就是让编译器把该左值识别为右值,意味着这个左值的资源有可能会被转移走哦
  2. 慎用move,除非你做好了该左值的资源可能会被转移走的准备

在这里插入图片描述
其实所谓编译器会将传值返回时的左值识别为右值的方法
就是return move(tmp);
但是这个move到底为什么这么厉害呢?
现阶段不建议深入研究move,语法过于复杂
在这里插入图片描述
为什么编译器要帮我们隐含的加上move呢?
因为
编译器要向前兼容,C++11之前的程序也要让他们既能够使用之前的拷贝构造,也能够使用现在的移动语义,使用时无需加上move
否则想要使用移动语义还要把所有传值返回的地方都加上move,那太麻烦了吧

而且他也怕我们忘了加

四.STL库的变化

自从C++11引入了右值引用之后,整个STL库的所有容器都添加了移动语义和右值引用版本的插入
在这里插入图片描述
我们来看一下
在这里插入图片描述

五.改造list的insert,使其支持右值引用类型

在这之前,我们要先补充一个知识点,要不然不好改造
在这里插入图片描述
那么右值引用本身是左值还是右值呢?
有const左值引用,那么有const右值引用吗?

1.补充

在这里插入图片描述
我们可以看出右值引用本身可以取地址,说明右值引用本身是左值
而且的确存在const右值引用

1.为什么右值引用本身是左值呢?

我们可以反过来想,如果右值引用是右值,而又因为右值不可被修改
所以移动构造和移动赋值当中swap根本就调用不了
就算能调用,也没法修改右值啊,所以右值引用不能是右值,而必须要是左值
在这里插入图片描述

2.const右值引用的价值是什么

我们知道const修饰一个变量限制该变量不能被修改
而且右值引用本身是左值,也就意味着右值引用所引用的右值本身可以被修改(因为引用就是取别名,无论你是左值引用还是右值引用)
因此我们就想通了,const右值引用就是限制右值引用本身/右值引用所引用的右值本身不能被修改
在这里插入图片描述
没毛病

3.右值引用的底层与右值引用的修改

在这里插入图片描述
在这里插入图片描述

2.改造

list的模拟实现的源代码在里面,太长了,我就不放过来了
C++ list模拟实现
在这里插入图片描述

void push_back(T&& x)
{
	insert(end(), move(x));
}
iterator insert(iterator pos, T&& x)
{
	Node* newnode = new Node(move(x));
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	newnode->_next = cur;
	cur->_prev = newnode;
	newnode->_prev = prev;
	prev->_next = newnode;
	++_size;
	return iterator(newnode);
}
list_node(T&& x)
	:_data(move(x))
	, _next(nullptr)
	, _prev(nullptr)
{}

其他的push_front我们就不加了,因为我们只是试一下来更好的了解库里面如何做的而已

3.验证

在这里插入图片描述
在这里插入图片描述
C++11为了方便我们进行使用,引入了万能引用和完美转发的语法

六.右值引用使用时的注意事项

  1. 右值引用作为参数时大部分情况下都不要加const,因为只要使用右值引用作为参数,肯定就是要从这个右值身上转移点资源,你用const去修饰那不前后矛盾吗
  2. 右值引用一般不作为返回值类型
    给左值引用一样,右值引用作为返回值类型时也会有违法(导致无法拿到想要的结果)和犯罪(野指针/野引用)两种可能性

对于自定义类型:
在这里插入图片描述
对于内置类型
在这里插入图片描述
在这里插入图片描述
此时跟左值引用返回是一样的情况

七.万能引用

万能引用:既能接收左值,也能接收右值,也能接受const左值和const右值
万能引用会根据传入的实参类型进行推导
传入左值,那么t就是左值引用类型
传入右值,那么t就是右值引用类型

template <typename T>
void PerfectForward(T&& t)
{}
//万能引用
template <typename T>
void PerfectForward(T&& t)
{}
int main()
{
	int x = 1;

	PerfectForward(x);//左值
	PerfectForward(10);//右值
	PerfectForward(move(x));//右值

	const int y = 20;
	PerfectForward(y);//const左值
	PerfectForward(move(y));//const右值
	return 0;
}

在这里插入图片描述
在这里插入图片描述
如果没有万能引用的话,针对于刚才的情况,我们就要写4个函数模板:
在这里插入图片描述
行是行,可是没有必要啊,因为函数模板的需要编译器自己推演的
因此C++11委员会就想着,反正都是编译器做,那就让编译器支持一下,这4个版本我们只保留右值引用的那个版本,不过这个T&&并不单指右值引用,而是万能引用

传什么值什么类型我就是什么值什么类型的引用
因此被称为万能引用

八.完美转发

有了万能引用之后,有个场景就尴尬了
因为右值引用的属性是左值,因此想要让它以右值的属性传递下去就需要move一下
而左值引用的属性也是左值,move之后也会变成右值,可是左值引用就不该被move啊

因此这个场景就很难解决,因此C++11委员会在提出万能引用的同时提出了完美转发

作用是:在传参的时候保持右值引用原有的右值属性
同时保持左值引用本身的左值属性

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(std::forward<T>(t));
}
int main()
{
	int x = 1;

	PerfectForward(x);//左值
	PerfectForward(10);//右值,右值引用再传递时属性是左值
	PerfectForward(move(x));//右值

	const int y = 20;
	PerfectForward(y);//const左值
	PerfectForward(move(y));//const右值
	return 0;
}

在这里插入图片描述

九.新的默认成员函数

在这里插入图片描述
为什么条件这么苛刻呢?
我们发现,凡是需要我们自己实现析构,拷贝构造,赋值运算符重载的类肯定是要进行深拷贝的类,
也就是说这三个成员函数是绑定实现的
因此条件这么苛刻也完全正确

//Student类没有移动构造,移动赋值,析构,拷贝构造,赋值运算符重载
//编译器会默认生成移动构造,移动赋值
class Student
{
public:
	Student(const char* name="wzs", int age=20)
		:_name(name)
		,_age(age)
	{}
private:
	wzs::string _name;//调用wzs命名空间中的string的移动构造和移动赋值
	int _age;
};

int main()
{
	Student s1;
	Student s2 = s1;//s1是左值,调用拷贝构造
	Student s3 = move(s1);//move(s1)是右值,调用编译器默认生成的移动构造
	Student s4;
	s4 = move(s2);//move(s2)是右值,调用编译器默认生成的移动赋值
	return 0;
}

在这里插入图片描述

以上就是C++11: 右值引用,移动语义,万能引用,完美转发,新的默认成员函数
的全部内容,希望能对大家有所帮助!!

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

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

相关文章

智慧工厂EMS能效管理解决方案

安科瑞电气股份有限公司 祁洁 15000363176 一、传统工厂现状 1、缺乏顶层设计&#xff0c;智慧化建设碎片化&#xff0c;不成体系&#xff0c;建成即落后。 2、弱电系统、网络、数据中心等基础设施老化&#xff0c;服务感知差。 3、缺乏设备在线监视&#xff0c;无法及时…

Vue+OpenLayers7入门到实战:OpenLayers如何销毁已经创建好的地图容器

返回《Vue+OpenLayers7》专栏目录:Vue+OpenLayers7入门到实战 前言 本章介绍如何使用OpenLayers7在地图上如何销毁已经创建好的地图容器。 在某些场景下,可能会需要销毁之前的地图,重新创建新的地图的需要,因此本章介绍一下在开始创建地图前如何先销毁之前的地图的功能。…

大数据采集平台-数据采集和集成技术和工具整理|电商实时数据API接口

今天谈下大数据平台构建中的数据采集和集成。在最早谈BI或MDM系统的时候&#xff0c;也涉及到数据集成交换的事情&#xff0c;但是一般通过ETL工具或技术就能够完全解决。而在大数据平台构建中&#xff0c;对于数据采集的实时性要求出现变化&#xff0c;对于数据采集集成的类型…

Unity类银河恶魔城学习记录12-6.5 p128.5 Create item by Craft源代码

此章节在原视频缺失&#xff0c;此过程为根据源代码推断而来&#xff0c;并非原视频步骤 Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩…

大创项目推荐 深度学习 大数据 股票预测系统 - python lstm

文章目录 0 前言1 课题意义1.1 股票预测主流方法 2 什么是LSTM2.1 循环神经网络2.1 LSTM诞生 2 如何用LSTM做股票预测2.1 算法构建流程2.2 部分代码 3 实现效果3.1 数据3.2 预测结果项目运行展示开发环境数据获取 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天…

HarmonyOS NEXT应用开发之@Observed装饰器和\@ObjectLink装饰器:嵌套类对象属性变化

上文所述的装饰器仅能观察到第一层的变化&#xff0c;但是在实际应用开发中&#xff0c;应用会根据开发需要&#xff0c;封装自己的数据模型。对于多层嵌套的情况&#xff0c;比如二维数组&#xff0c;或者数组项class&#xff0c;或者class的属性是class&#xff0c;他们的第二…

对于Redis,如何根据业务需求配置是否允许远程访问?

1、centos8 Redis安装的配置文件目录在哪里&#xff1f; 在 CentOS 8 中&#xff0c;默认情况下 Redis 的配置文件 redis.conf 通常位于 /etc/ 目录下。确切的完整路径是 /etc/redis.conf。 2、redis如何设置允许远程登录 修改redis.conf文件 # 继承默认注释掉的bind配置 # …

金三银四面试题(十五):Java基础问题(6)

这部分面试题多用于面试的热身运动&#xff0c;对很多找实习和准备毕业找工作的小伙伴至关重要。 HashMap与ConcurrentHashMap 都是key-value 形式的存储数据&#xff1b; HashMap 是线程不安全的&#xff0c;ConcurrentHashMap 是JUC 下的线程安全的&#xff1b; HashMap 底层…

【前端】CSS(引入方式+选择器+常用元素属性+盒模型+弹性布局)

文章目录 CSS一、什么是CSS二、语法规范三、引入方式1.内部样式表2.行内样式表3.外部样式 四、选择器1.选择器的种类1.基础选择器&#xff1a;单个选择器构成的1.标签选择器2.类选择器3.id 选择器4.通配符选择器 2.复合选择器1.后代选择器2.子选择器3.并集选择器4.伪类选择器 五…

RobotFramework测试框架(12)--第三方库

Library 关于射频指南 |机器人框架 (robotframework.org) 使用RF需要使用Library&#xff0c;常用的第三方库如下&#xff1a; 在web浏览器中进行web应用程序测试可以使用的库是 Selenium Library 在内部使用流行的 Selenium 工具的 Web 测试库Browser Library 由 Playwri…

【vite】-【vite介绍】-【vite的基础应用】-【vite的高级应用】-【

目录 vite介绍vite的基础应用vite创建项目vite创建vue3项目vite创建vue2项目vite创建react项目 vite中使用css的各种功能vite中使用tsvite中处理静态资源的方法vite集成eslint和prettiervite中的env环境变量 vite的高级应用 vite介绍 一、特点&#xff1a; 开发时效率极高开箱…

华为ICT七力助推文化产业新质生产力发展

创新起主导作用的新质生产力由新劳动者、新劳动对象、新劳动工具、新基础设施等四大要素共同构成&#xff0c;符合新发展理念的先进生产力质态&#xff1b;具有高科技、高能效、高质量等三大突出特征。而通过壮大新产业、打造新模式、激发新动能&#xff0c;新质生产力能够摆脱…

QT 使用QMediaPlayer实现的简易视频播放器

文章目录 效果图功能点类介绍代码介绍总结 QT 使用QMediaPlayer实现的简易视频播放器 效果图 功能点 播放指定视频全屏/退出全屏开始/暂停/重置视频拖拽到指定位置播放 类介绍 需要在配置文件中加入Multimedia, MultimediaWidgets这俩个库。Multimedia&#xff1a;提供了一套…

【Qt 学习笔记】Qt 中出现乱码的解释及讨论

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt 中出现乱码的解释及讨论 文章编号&#xff1a;Qt 学习笔记 / 06 文…

FJSP:蜣螂优化算法( Dung beetle optimizer, DBO)求解柔性作业车间调度问题(FJSP),提供MATLAB代码

一、柔性作业车间调度问题 柔性作业车间调度问题&#xff08;Flexible Job Shop Scheduling Problem&#xff0c;FJSP&#xff09;&#xff0c;是一种经典的组合优化问题。在FJSP问题中&#xff0c;有多个作业需要在多个机器上进行加工&#xff0c;每个作业由一系列工序组成&a…

一种有效的隐私保护联邦学习方法-文章翻译

一种有效的隐私保护联邦学习方法 摘要 联邦学习已成为协作学习和隐私保护学习的一种很有前途的方法。联合学习过程中的参和者通过交换模型参数而不是实际的训练数据(他们可能希望保持私有)来合作训练模型。然而,参数交互和生成的模型仍然可能会披露有关所用训练数据的信息…

Mysql故障解析

目录 一、Mysql单实例故障排查 1.故障一 2.故障二 3.故障三 4.故障四 5.故障五 6.故障六 7.故障七 8.故障八 二、Mysql主从故障排查 1.故障一 2.故障二 3.故障三 三、Mysql优化 1.硬件方面 &#xff08;1&#xff09;关于CPU &#xff08;2&#xff09;关于内…

在线监测系统在水厂水质管理工程中的应用与研究

【摘要】&#xff1a;随着水厂水质管理技术和管理水平的提升&#xff0c;达到了在线监测系统通过监测数据的反馈&#xff0c;及时发现问题&#xff0c;快速处理事故&#xff0c;优化了水资源的利用率&#xff0c;提高了供水系统的稳定性和安全性&#xff0c;从而有效地提高供水…

2024.4.4-day09-CSS 布局模型(标准流模型、浮动模型)

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 作业 2024.4.4-学习笔记1 CSS 布局模型1.1 标准流1.2 CSS 浮动1.3 去除塌陷 2…

RabbitMQ的交换机与队列

一、流程 首先先介绍一个简单的一个消息推送到接收的流程&#xff0c;提供一个简单的图 黄色的圈圈就是我们的消息推送服务&#xff0c;将消息推送到 中间方框里面也就是 rabbitMq的服务器&#xff0c;然后经过服务器里面的交换机、队列等各种关系&#xff08;后面会详细讲&am…