【C++11】右值引用

目录

initializer_list

decltype

nullptr

右值引用

什么是右值

左值引用如何引用右值

左值引用解决了那些问题

右值引用要解决问题

引用折叠和完美转发


C++11是C++编程语言继98年之后最重要的一次版本更新,那么这篇博客就来具体谈一谈都更新了什么

首先就是列表初始化,就是用花括号去初始化任何数据,比如整形,数组,结构体可以这样初始化

下面的日期类也是只调用构造

我们在new一个日期类对象的数组的时候就可以这么写

initializer_list

对于一些容器我们可以这样初始化

它其实是去调用构造的,在C++11后增加了一些构造函数

所以initializer_list其实是一个类型

如何让我们之前自己实现的vector支持这个构造呢,我们再写一个构造函数就可以

decltype

这个关键字可以用来推导类型并作为类型

我们之前也说过typeid,它可以告诉变量的类型,我们来举例用一下

我们可以看到x的类型中并没有const,但是p1的类型中有const,因为前者是修饰x的内容不改变,也叫顶层const;而后者是修饰p1指向的内容不被修改,也叫底层const。我们可以认为顶层const就是一种修饰,修饰变量的权限,而底层const是具体的变量类型。

nullptr

我们C语言有NULL,那为什么后来弄了一个nullptr呢?肯定是NULL会有一些问题,NULL其实在C语言中就是0去强转成void*的

在这种情况下,在C++中有时会有隐式类型转换,这样就会在指针和整数之间相互转换,而nullptr是一个特殊的类型,只能转化成指针类型,所以NULL就会出现一些问题,比如:

这样是可以的,但是NULL会进行隐式类型转换,nullptr不会

所以使用nullptr就会避免这种潜在的混淆和错误

右值引用

什么是右值

下面就到了右值引用的部分,之所以发明右值引用,肯定是因为之前的左值引用有一些问题不能解决,那下面我们就来具体的看一下

首先我们要明白什么是左值,什么是右值,其实左值和右值之间最根本的区别是能否取地址。有人可能说左值可以改变,右值无法改变,其实这是不对的,const修饰的左值也无法改变,而右值确实都无法改变。那我们下面先来举几个例子

 

之前我们说的都是左值引用,我们已经会写了,右值引用的话就是两个&符号

在这里我们需要记住右值被右值引用后,右值引用的属性是左值,可能很绕嘴和抽象,举上面的例子,10是右值,它被右值引用后rr1是左值,可以取地址,所以rr2和rr3都是左值。那么为什么会是这样呢?因为我们后面有应用场景就是换取右值的资源,所以它就得必须变成左值,因为右值是不可变的。

左值引用如何引用右值

左值引用不能引用右值,但是const左值引用可以,因为加const就证明不能修改了;同理,右值引用也不能引用左值,但是右值引用可以引用move之后的左值。我们来举个例子

左值引用解决了那些问题

说完了上面的一些基本语法,那接下来就来看看右值引用的应用,它究竟解决了那些左值引用无法解决的问题呢?

说之前,我们先来看看左值引用究竟解决了哪些问题,首先就是传参可以用左值引用传参,这样就解决了传参的拷贝问题;其次就是函数传返回值的话,如果出了函数这个返回值还在,那么就可以用左值引用返回。那我们就看一下用左值引用和不用左值引用的区别,利用下面这个代码

jxh::mystring& stradd(jxh::mystring& s)
{
	static::jxh::mystring s2;

	for (int i = 0; i < s.size(); i++)
	{
		s2 += s.c_str()[i]+1;
	}
	return s2;
}

int main()
{
	jxh::mystring s1("abcdefgh");
	jxh::mystring s3;
	s3=stradd(s1);
	return 0;
}

首先我在函数中不管是传参还是传返回值都用上左值引用,可以发现结果是

然后把引用去掉,再来看结果

我们可以发现多了两次拷贝构造,可以发现左值引用确实可以减少深拷贝,左值引用解决了上述问题

右值引用要解决问题

但是左值引用仍然有未解决的问题,那就是如果返回的是一个局部对象,那不能用左值引用了,这里仍然会有拷贝问题,就是局部对象要拷贝给一个临时对象,而这个临时对象很快就会被销毁,那么我们能不能利用这个临时对象中的资源呢?这是右值引用要解决的问题。

那么它具体是如何实现的呢?我们用这个代码举例

首先肯定不能右值引用返回,因为s2是左值,就算我们move一下也不行,因为s2毕竟要销毁;其次我们知道,s2会拷贝构造一个临时对象,然后临时对象赋值给s3,这里的拷贝构造和赋值全是深拷贝,我们可以从这里下手,我们让s2变成右值(为了让以前的代码也享受这样的资源节省,所以编译器会隐式将s2move),然后重载拷贝构造叫做移动构造,参数是右值引用,意思是移动走s2中的资源给临时对象,并且因为移动构造比拷贝构造的参数更匹配,所以就回去调用移动构造,我们可以这么写移动构造

	mystring(mystring&& s)
		{
			swap(_start, s._start);
			swap(_size, s._size);
			swap(_capacity, s._capacity);
			cout << "移动构造——浅拷贝" << endl;
		}

并且临时对象也是右值,我们也可以利用上面的原理把它里边的内容转移给s3,这就叫做移动赋值

	mystring& operator=(mystring&& s)
		{
			swap(_start, s._start);
			swap(_size, s._size);
			swap(_capacity, s._capacity);
			cout << "移动赋值——浅拷贝" << endl;
			return *this;
		}

上面的移动构造和移动赋值都是浅拷贝,基于上面的原理,我们就叫右值引用的移动语义,于是我们可以看看有移动语义和没有之间的差别有多大

我们可以看到移动语义确实减少了深拷贝,把没有必要的深拷贝都变成了浅拷贝,更充分的利用了申请的资源。

我们如果不在主函数先构造在赋值,而是直接用函数的返回值进行拷贝构造的话是这样的:

如果没有移动语义,那么s2会拷贝构造出一个临时对象,临时对象在拷贝构造出s3,但是之前编译器就已经会给我们优化了(直接在函数退出之前由s2构造出s3),就是优化成一次拷贝构造

之后有了移动语义,那么就是两次移动构造,同样优化成一次移动构造

C++11对右值概念进行了细分,为了方便理解,分为:

1.纯右值(内置类型的右值)如:10 x+y

2.将亡值(自定义类型的右值,其实就是生命周期非常短暂)如:匿名对象、传值返回函数

右值还有一个应用场景就是用于容器插入,对于有名对象插入的时候肯定要深拷贝一份,但是对于匿名对象和进行隐式类型转换的数据它可以直接移动拷贝

我们可以看到,确实是这么回事

我们上面说,右值被右值引用后,右值引用的属性是左值,因为左值才可以被改变,这样资源才能被转移,我们来证明一下

所以我们在调用移动构造和移动赋值时,那里的参数是左值,因为只有这样才能转移资源

mystring(mystring&& s)
		{
			swap(_start, s._start);
			swap(_size, s._size);
			swap(_capacity, s._capacity);
			cout << "移动构造——浅拷贝" << endl;
		}
mystring& operator=(mystring&& s)
		{
			swap(_start, s._start);
			swap(_size, s._size);
			swap(_capacity, s._capacity);
			cout << "移动赋值——浅拷贝" << endl;
			return *this;
		}

引用折叠和完美转发

接下来是模板和引用的结合,我们知道模板可以进行推演,把活都交给编译器去干,所以就有了这么一个叫做引用折叠(也叫万能引用)的东西,就是不管是左值还是右值都可以传给这个模板,它的基本形式是这样的

别看这里有两个&,其实左值和右值都可以传进来这个函数,传左值两个&符号就变成了一个,所以叫折叠

这些都是可以传的,其实编译器在实例化这个模板函数的时候,将传左值,传右值都实例化了一份

但是还有一个问题,就是右值传过去t就变成了左值,但是我还想用它右值的属性在调一个函数怎么办呢?这时就用到了完美转发,就是把t放到里边,它原来是左值还是右值都会恢复回去,基本使用是这样的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值