右值引用全面剖析

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

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

右值引用的出现

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

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

引用折叠规则

相关推荐
我命由我123451 小时前
Spring Boot 自定义日志打印(日志级别、logback-spring.xml 文件、自定义日志打印解读)
java·开发语言·jvm·spring boot·spring·java-ee·logback
徐小黑ACG2 小时前
GO语言 使用protobuf
开发语言·后端·golang·protobuf
0白露3 小时前
Apifox Helper 与 Swagger3 区别
开发语言
Tanecious.4 小时前
机器视觉--python基础语法
开发语言·python
叠叠乐4 小时前
rust Send Sync 以及对象安全和对象不安全
开发语言·安全·rust
Tttian6225 小时前
Python办公自动化(3)对Excel的操作
开发语言·python·excel
Merokes6 小时前
关于Gstreamer+MPP硬件加速推流问题:视频输入video0被占用
c++·音视频·rk3588
独好紫罗兰6 小时前
洛谷题单2-P5713 【深基3.例5】洛谷团队系统-python-流程图重构
开发语言·python·算法
闪电麦坤957 小时前
C#:base 关键字
开发语言·c#
Mason Lin7 小时前
2025年3月29日(matlab -ss -lti)
开发语言·matlab