右值引用全面剖析

为什么要有右值引用,右值引用出现前程序员们的困境:

在右值引用出现以前,想要把一块内存空间里的内容放到另一块内存空间,只能再开辟一块内存,然后将原来内存里的内容复制到新开辟的内存里,然后再把原来的内存清理掉,这样操作有很大的空间分配和清理的消耗 ,还容易产生内存碎片

右值引用的出现

右值引用出现使得移动构造和移动赋值得以实现

移动就是指针所有权的转移,原来的 指针1 指向了一块内存,现在再新建一个指针 指针2 指向这块内存,然后让原来的指针1指向空,就完成了一次资源转移,从而实现了右值引用的 终极目标减少复制,提升性能

移动构造没有内存复制,取而代之的是简单的指针替换,没有新内存的申请和旧内存的释放

cpp 复制代码
CharBuffer::CharBuffer(CharBuffer&& temp):size(temp.size),buf(temp.buf)
{
	cout << "移动构造函数" << endl;
	temp.buf = nullptr;
	temp.size = 0;
}

不就是指针转移嘛,反正都是代码实现,为什么拷贝构造里不能进行一样的操作来实现移动语义呢?

因为拷贝构造函数的传参是const无法把原指针指向空,于是会出现两个对象指向同一片内存空间的局面,这时候就容易造成一片空间两次析构的危险

cpp 复制代码
CharBuffer::CharBuffer(const CharBuffer& temp) :size(temp.size), buf(new char[size])
{
	cout << "拷贝构造函数" << endl;
	memcpy(buf, temp.buf, size);
}

如果没有自己定义的移动构造函数,通过右值传参其实也是调用的拷贝构造,因为拷贝构造函数的参数是 const 加上const以后,传参就变成了常量,反正也无法被改变,编译器就会自动做出优化,使右值匹配到拷贝构造函数

cpp 复制代码
{
	CharBuffer test1;
	CharBuffer test2(move(test1));
}

同理,传参是 const的都可以接收右值

此外右值往往都是一些临时变量 ,比如函数返回值,当一个对象的生命周期即将结束,而我们又希望在后续继续使用这个对象,并且又不想用拷贝,因为会产生复制开销 ,此时就可以通过右值移动来实现延长临时对象的生命周期(将亡值)

std::move()

通过move源代码可以看到

cpp 复制代码
template <class _Ty>
remove_reference_t<_Ty>&& move(_Ty&& _Arg) {
    return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}

move其实就是把传进来的变成右值引用返回出去(传左值引用就会通过remove_reference移除掉引用变回原本的类型)

所以通过move给一个对象赋值以后如果在对象里没有特别处理的话,原来的对象其实是可以继续用的

就像下面这段代码,是不会报错的,但是会有提醒test1是已经被移动过的对象

cpp 复制代码
{
//错误示范
	CharBuffer test1;

	CharBuffer test2(move(test1));

	auto test3 = test1;

	cout << test3.size << endl;
}

所以大家其实都是在遵守move结束后不再使用原来的已经被移动了的对象的君子约定,这样更贴合移动的语义,即原对象已经被移动到新对象里,其实原对象还是可以继续使用的(但不要这样做!)

通过forward和万能引用实现完美转发(主要是通过引用折叠推导出是左值引用还是右值引用)

无论一个函数的实参是左值还是右值,他的形参都是左值,所以右值传进函数以后会变成左值,

就像这个里面,不管传进去的是左值还是右值,在TestFunc1里面形参都会变成左值然后调用到左值引用

cpp 复制代码
void TestFunc2(const string& temp) {

	cout << "左值引用" << endl;
}
void TestFunc2(string&& temp) {

	cout << "右值引用" << endl;
}

void TestFunc1(string& temp)
{
	TestFunc2(temp);
}

void TestFunc1(string&& temp)
{
	TestFunc2(temp);
}
int main(int argc, char* argv[])
{

	TestFunc1(string("Jolly"));
	cout << "==============" << endl;
	string test1("Jolly");
	TestFunc1(test1);
	
	getchar();
	return 0;
}

此时就需要实现完美转发来解决

cpp 复制代码
void TestFunc1(string& temp)
{
	TestFunc2(static_cast<const string&>(temp));//方法1显式强制转换
	TestFunc2(std::forward<const string&>(temp));//方法2 forward万能引用模板实现完美转发
}

void TestFunc1(string&& temp)
{
	TestFunc2(static_cast<string&&>(temp));
	TestFunc2(std::forward<string&&>(temp));
}

通过forward源代码可以看到forward就是把传入进来的都先去引用变回原来的类型,然后经过万能引用推导出应该返回什么类型的引用

cpp 复制代码
template <class _Ty>
 _Ty&& forward(remove_reference_t<_Ty>& _Arg) {
    return static_cast<_Ty&&>(_Arg);
}

template <class _Ty>
 _Ty&& forward(remove_reference_t<_Ty>&& _Arg) {
    return static_cast<_Ty&&>(_Arg);
}

同时也可以通过万能引用模板TestFun1函数换模板函数

cpp 复制代码
template<class T>;
void TestFunc1(T&& temp)//这是万能引用,不是右值引用哦
{
	TestFunc2(std::forward<T>(temp);
} 

引用折叠规则

相关推荐
AmosCloud20137 分钟前
3.5 字典树(Trie)与后缀树
开发语言·数据结构·链表·c#
Tiger Z25 分钟前
R 语言科研绘图第 13 期 --- 柱状图-堆叠
开发语言·程序人生·r语言·贴图
我命由我1234533 分钟前
26.Java Lock 接口(synchronized 关键字回顾、可重入锁快速入门、Lock 对比 synchronized)
java·开发语言·后端·java-ee·intellij-idea·intellij idea·后端开发
nixiaoge39 分钟前
组网实训实现
开发语言·php
graceyun1 小时前
牛客网刷题 ——C语言初阶(5操作符)——BC111 小乐乐与进制转换
c语言·开发语言·算法
JoneMaster1 小时前
[读书日志]从零开始学习Chisel 第二篇:Scala的变量与函数(敏捷硬件开发语言Chisel与数字系统设计)
开发语言·学习·scala
小_太_阳1 小时前
Scala_【5】函数式编程
开发语言·后端·scala·intellij-idea
hummhumm2 小时前
第8章 汇编语言--- 循环结构
java·运维·开发语言·汇编·数据结构·算法·汇编语言
奇偶变不变2 小时前
30分钟学会LaTex
开发语言·前端·javascript
sara_shengxin2 小时前
C#调用Lua
开发语言·c#·lua