【C++】左值与右值
日拱一卒,功不唐捐
日常工作中经常遇到右值引用的相关逻辑,或者想要通过右值引用来优化程序性能,不过因为对右值引用只有一知半解,因此常常需要查阅资料才能拼凑出只鳞片羽的局部印象,难以构建整体理解,这里将平时搜集到的一些资料与自己的一些理解汇总到一起,从而避免每次的重复搜索与整理。
这里由于不是(也没有时间)系统的去搜集所有的内容,因此输出的结论会显得凌乱与分散,寄希望于等到内容丰富到一定程度再进行梳理。
C++的值类别
value总的来说,可以分成glvalue跟rvalue两类,其中glvalue指的是泛左值(generalized lvalue),rvalue为右值。
泛左值包含左值lvalue与将亡值xvalue(expiring value,快要销毁的对象),而右值rvalue同样包含纯右值prvalue(pure rvalue)与将亡值xvalue。
这里就有人要问了,将亡值这个反骨崽到底是左值还是右值?我只能说视情况而定,根据需要,可以是左值,也可以是右值,举个例子:
MoveTest GetMoveTestInstance()
{
MoveTest t;
return t;
}
MoveTest k = GetMoveTestInstance();
上面这个构造函数,如果我们为MoveTest类定义了移动构造函数,那么GetMoveTestInstance中的将亡值t就是右值,否则就是左值。
具名的右值是左值
所谓具名的右值,指的是如果某个用&&表示的对象有了名字,那么这个对象就会被当成左值来使用,下面给个例子:
#include <iostream>
#include <vector>
#include <set>
#include <assert.h>
using namespace std;
class MoveTest
{
public:
int i = 0;
MoveTest(int data) : i( data )
{
cout << "Move Test Constructor" << endl;
}
MoveTest( const MoveTest& other ) : i(other.i)
{
cout << "Move Test Copy Constructor" << endl;
}
MoveTest( MoveTest&& other ) : i( other.i )
{
cout << "Move Test Move Constructor" << endl;
}
MoveTest& operator=( const MoveTest& other )
{
if( this != &other )
{
i = other.i;
cout << "Move Test Copy Assignment" << endl;
}
return *this;
}
MoveTest& operator=( MoveTest&& other )
{
if( this != &other )
{
i = other.i;
cout << "Move Test Move Assignment" << endl;
}
return *this;
}
};
void CheckCopy( MoveTest& Input )
{
MoveTest Local = Input;
cout << "CheckCopy:" << Local.i << endl;
}
void CheckMoveAssign( MoveTest&& Input )
{
MoveTest Local( 0 );
Local = Input;
cout << "CheckMoveAssign:" << Local.i << endl;
}
void CheckMove( MoveTest&& Input )
{
MoveTest Local = Input;
cout << "CheckMove:" << Local.i << endl;
}
void Test()
{
MoveTest Original( 3 );
CheckCopy( Original );
//CheckMove( Original ); // error: an rvalue cannot be bound to an lvalue
CheckMove( move(Original) );
CheckMoveAssign( MoveTest(2) );
}
int main()
{
Test();
return 0;
}
运行结果为:
Move Test Constructor
Move Test Copy Constructor
CheckCopy:3
Move Test Copy Constructor
CheckMove:3
Move Test Constructor
Move Test Constructor
Move Test Copy Assignment
CheckMoveAssign:2
首先,我们要想将一个左值赋值给一个右值的形参,需要调用std::move接口进行显式转换;
其次,在CheckMove函数中,MoveTest Local = Input;我们理解应该调用移动构造函数进行构造,但实际上调用了拷贝构造函数,这就是前面说的具名的右值会被看成是左值导致的,既然是左值,那么自然使用拷贝构造函数。
既然具名的右值是左值,那么这种右值有什么作用呢?注意,右值提出的主要目的是降低数据拷贝的时间与空间消耗,而即使被看成左值来使用,这个避免拷贝的特性还依然是正常使用的。
另一个问题是,如果我们想要对具名的右值调用移动构造函数要怎么办,通过传参数的方式真的没有办法做到了吗?当然,不是,C++为我们提供了将左值转换为右值使用的接口std::move,我们只需要将函数实现改为如下形式,就能够调用移动构造函数了:
void CheckMove( MoveTest&& Input )
{
MoveTest Local = move(Input);
cout << "CheckMove:" << Local.i << endl;
}