右值引用全面剖析

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

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

右值引用的出现

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

移动就是指针所有权的转移,原来的 指针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);
} 

引用折叠规则

相关推荐
dorabighead15 分钟前
JavaScript 高级程序设计 读书笔记(第三章)
开发语言·javascript·ecmascript
风与沙的较量丶1 小时前
Java中的局部变量和成员变量在内存中的位置
java·开发语言
水煮庄周鱼鱼1 小时前
C# 入门简介
开发语言·c#
澄澈天空1 小时前
C++ MFC添加RichEditControl控件后,程序启动失败
c++·mfc
编程星空2 小时前
css主题色修改后会多出一个css吗?css怎么定义变量?
开发语言·后端·rust
软件黑马王子2 小时前
Unity游戏制作中的C#基础(6)方法和类的知识点深度剖析
开发语言·游戏·unity·c#
Lzc7742 小时前
C++初阶——简单实现vector
c++·简单实现vector
Logintern092 小时前
使用VS Code进行Python编程的一些快捷方式
开发语言·python
Multiple-ji2 小时前
想学python进来看看把
开发语言·python
一个小白12 小时前
C++——list模拟实现
开发语言·c++