欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 美食 > 【C++11】右值引用与移动语义

【C++11】右值引用与移动语义

2025/1/30 16:11:20 来源:https://blog.csdn.net/qq_72680384/article/details/145388786  浏览:    关键词:【C++11】右值引用与移动语义

目录

  • 什么是左值 / 右值
    • 举例
    • 左值引用,右值引用
    • 常量左值引用
  • 移动语义
    • 注意事项

什么是左值 / 右值

在C++中,所有的值如果不是左值,就是右值。

左值是指表达式结束后依然存在的持久化对象,右值是指表达式结束后就不再存在的临时对象。

  • 有名字的对象都是左值,右值没有名字。
  • 还有一个可以区分左值和右值的便捷方法:看能不能对表达式取地址,如果能,则为左值,否则为右值。

举例

int ii = 3;    		//ii是左值,可以取地址,3是右值,无法取地址
int  jj = ii+8; 	//jj是左值,ii+8是右值
T a  = getTemp();   //a是左值,getTemp函数的返回值是右值(临时变量)//如果getTemp返回的是引用,则是左值

左值引用,右值引用

C++98中的引l用很常见,就是给变量取个别名,在C++11中,因为增加了右值引l用的概念,所以C++98中的引用都称为了左值引l用。

  • 右值引用就是给右值取个名字。
  • 语法:数据类型&& 变量名 = 右值;
  • 功能:右值引用就是给右值起名,一旦完成起名,则从临时变量(右值)变成了变量(左值),临时变量(右值)获得新生,也就是拥有了新的生命周期,只要变量名字存在,就一直存活。
#include <iostream>
using namespace std;class AA
{
public:int m_a = 9;
};AA getTemp()
{return AA();
}int main()
{int&& a = 3;	//3是右值int b = 8;		//b是左值int&& c = b + 5;//b+5是右值AA&& aa = getTemp(); //getTemp()的返回值是右值//右值一旦取了名字,就成了普通变量,也就是成了左值,a是左值,c是左值,aa也是左值cout << a << endl;cout << c << endl;cout << aa.m_a << endl;return  0;
}

常量左值引用

常量左值引用是个奇葩,它可以算是一个万能的引用类型,它可以绑定左值、右值,而且在绑定右值的时候,常量左值引用还可以像右值引用一样将右值的生命期延长,缺点是,只能读不能改。

语法:const 类型& 变量名 = 左值/右值

在函数传参的时候有时候会用到,挺重要的

移动语义

右值引用的目的就是为了实现移动语义。

引入:

如果一个对象中有堆区资源,需要编写拷贝构造函数和赋值函数,实现深拷贝。
深拷贝把对象中的堆区资源复制了一份,如果源对象(被拷贝的对象)是临时对象,拷贝完就没什么用了,这样会造成没有意义的资源申请和释放操作。
如果能够直接使用源对象拥有的资源,可以节省资源申请和释放的时间。C++11新增加的移动语义就能够做到这一点。

实现移动语义要增加两个函数:移动构造函数和移动赋值函数

移动构造函数的语法:类名(类名&& 源对象){}
移动赋值函数的语法:operator=(类名&& 源对象){}

功能:对右值使用移动语义,效率高很多

#include <iostream>
using namespace std;class AA
{
public:int* m_data = nullptr;	//数据成员AA(){}//给数据成员分配内存void alloc(){m_data = new int;		//分配内存memset(m_data, 0, sizeof(int));	//初始化}//拷贝AA(const AA& a){cout << "调用了拷贝构造函数\n";if (m_data == nullptr) alloc();		//如果没分配内存,就分配memcpy(m_data, a.m_data, sizeof(int));	//拷贝}//移动构造函数//如果参数是右值会调用这个AA(AA&& a){cout << "调用了移动构造函数\n";if (m_data != nullptr) delete m_data;		//如果没分配内存,就分配m_data = a.m_data;a.m_data = nullptr;}//赋值AA& operator=(const AA& a){cout << "调用了赋值函数 \n";if (this == &a) return *this;	//避免自我赋值if (m_data == nullptr) alloc();memcpy(m_data, a.m_data, sizeof(int));	//拷贝return  *this;}//移动赋值函数//如果参数是右值会调用这个AA& operator=(AA&& a){cout << "调用了移动赋值函数 \n";if (this == &a) return *this;	//避免自我赋值if (m_data != nullptr) delete m_data;		m_data = a.m_data;a.m_data = nullptr;return  *this;}};int main()
{AA a1;a1.alloc();*a1.m_data = 3;cout << *a1.m_data << endl;AA  a2 = a1;	//调用拷贝构造函数cout << *a2.m_data << endl;AA a3;a3 = a1;	//调用赋值函数cout << *a3.m_data << endl;auto f = [] {AA aa; aa.alloc(); *aa.m_data = 8; return aa; };//返回AA类对象的lambda函数AA a4 = f();		//返回的临时变量作为右值,调用移动构造函数cout << *a4.m_data << endl;AA  a5;a5 = f();	//调用移动赋值函数cout << *a5.m_data << endl;return 0;
}

注意事项

  • 1)对于一个左值,会调用拷贝构造函数,但是有些左值是局部变量,生命周期也很短,能不能也移动而不是拷贝呢?
    • C++11为了解决这个问题,提供了std::move()方法来将左值转义为右值,从而方便使用移动语义。它其实就是告诉编译器,虽然我是一个左值,但不要对我用拷贝构造函数,用移动构造函数吧。
    • 左值对象被转移资源后,不会立刻析构,只有在离开自己的作用域的时候才会析构,如果继续使用左值中的资源,可能会发生意想不到的错误。
	AA a1;a1.alloc();*a1.m_data = 3;cout << *a1.m_data << endl;AA  a2 = move(a1);	//调用移动拷贝构造函数cout << *a2.m_data << endl;
  • 2)如果没有提供移动构造/赋值函数,只提供了拷贝构造/赋值函数,编译器找不到移动构造/赋值函数就去寻找拷贝构造/赋值函数。
  • 3)C++11中所有容器都实现了移动语义,避免对含有资源的对象发送无谓的拷贝。
  • 4)移动语义对于拥有资源(如内存,文件句柄)的对象有效,如果是基本类型,使用移动语义没有意义。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com