C++使用右值引用改进程序的性能
左值和纯右值(是否取地址)
int main()
{
int a= 10;//左值因为可以取地址&a
const int b= 20;//左值...
double dx = 23.34;//左值...
int *ip = nullptr;//左值...&ip
&10;//纯右值!&10
&20;//纯右值
}
总结:所有的具名变量或对象都是左值,而右值不具名
lambda是不具名的,但是是执行体
将亡值
程序在运行或计算过程中,产生的临时量或临时对象(这个临时对象不具名),称为将亡值;内置类型产生的字面值
int main()
{
int a = 10;
int b = 20;
int c = 0;
c =a + b;//这块有tmp将亡值
a = func();//返回时构造临时对象,是字面值,是将亡值,是纯右值
//因此不可以 func() = a; 赋值,因为字面量不可以赋值
}
函数的返回值
int func()
int X = 10;
return X ;
}
int main()
{
int a=10,b=10;
int i=0;
i= a+ b;
++i;
i++;//右值
&a;
a = func(); //返回8
return 0;
}
可以观察到这个函数的返回值,是需要一个将亡值的
问:是否每一个函数的返回值都需要将亡值?
不是的,返回值的参数是&引用
左值强转成右值
我们尽量使用C++的静态转换,因为安全
C的强转,不安全,注释解释很清楚了
class Int
{
private:
int val;
public:
Int(int x = 0) :val(x){}
void PrintValue() {
cout << "val = " << val << endl;
}
void SetValue(int val)
{
this->val = 200;
}
};
int main()
{
Int a(10);
const Int b(20);
Int&& rv = Int(30);//右值 //rv右值引用
//Int&& ra = (Int&&)a;
Int&& ra = static_cast<Int&&>(a);//这是C++提供的静态转换
Int&& rb = (Int&&)b;//不安全,b是常性左值,后续可能会改变b的值
//Int&& rb = static_cast<Int&&>(b);//不具有取常性
ra.PrintValue();
rb.PrintValue();
rb.SetValue(100);//我们使用的是C的强转方式,但b是const所以就会不安全
rb.PrintValue();
return 0;
}
常性左值引用是万能引用
编译器编译的时候,会将常性右值引用,编译成常性左值引用
Int fun(int x)
{
Int tmp(x);
return tmp;
}
int main()
{
Int a = fun(1);//此处有代码优化,自动将将亡值转换过去
//Int& b = fun(2);//xvalue b是左值引用(需要绑定到具名值上),而fun(2)是右值引用
//如何理解上面代码:首先返回值是将亡值,可以理解为字面量 2 ,2在后续不可以被修改了
const Int& c = fun(3);//常性引用接收将亡值时,会将将亡值的生存期固化
Int&& rf = fun(4);//也会将生存期固化
const Int& c1 = fun(30);//万能
Int&& c2 = fun(30);
return 0;
}
函数中的局部对象,不能以引用返回
返回值是左值引用的话,其实编译器会将函数用指针编译
局部对象已经死了
//Int * const fun (int x)
Int & func(int x)
{
Int tmp(x);
return tmp;//return & tmp;析构
}
int main()
{
Int a = func(1);//随机值
Int& b = func(2);//随机值
const Int& c = func(3);//随机值,tmp的值会残留在空间中
return 0;
}
//右值引用
Int && func(int x)
{
//Int tmp(x);
//return tmp;//左值
return Int(x);//会调用构造函数,创建不具名对象
}
int main()
{
Int a = func(1);//随机值
Int& b = func(2);//err
const Int& c = func(3);//随机值
Int&& rf = func(4);//随机值
return 0;
}
move语义
class testClass {
public:
int m_a;
int m_b;
testClass(int a) {
m_a = a;
m_b = 10;
}
testClass(int a, int b)
{
m_a = a;
m_b = b;
}
};
int main() {
vector<string> arr;
string s1 = "213";
arr.push_back(my_move(s1));
cout << "s1 = " << s1 << '\n';
vector<testClass> list;
testClass t1{ 3 };
testClass t2{ 4,9 };
list.push_back(t1);
list.push_back(std::move(t2));
for (auto& x : list)
cout << x.m_a << " " << x.m_b << endl;
cout << t2.m_a <<" " << t2.m_b << endl;
return 0;
}
/*
std::move 主要用于转移资源的所有权,但对于简单类型(如基本数据类型 int)而言,并没有资源的所有权需要转移。在这里,m_a 和 m_b 是 int 类型,它们的值会被移动,但没有堆内存、文件句柄等需要特殊处理的资源。
所以,虽然 t2 的成员变量的值被移动到了 list 中,但 t2 本身并没有被清空或销毁。你仍然可以在 main 函数中访问 t2 的成员变量,因为它们仍然存在,只是在 list 中的某个对象中。
*/
我们知道移动语义是通过右值引用来匹配临时值(将亡值,不具名对象)的,那么,普通的左值是否也能借助移动语义来优化性能呢,那该怎么做呢?
C++11为了解决这个问题,提供了std::move方法来将左值转换为右值,从而方便应用移动语义。move是将对象中资源的所有权从一个对象转移到另一个对象,只是转移,没有堆内存的申请,拷贝和释放。