C++中的表达式存在左值、右值之分,实际上这个语法表述继承自C语言,在C语言中,这两个语法词的意义很简单,左值表示可以在赋值语句左侧的表达式,右值表示可以在赋值语句右侧的表达式。
但是C++中对这两个语法的语义进行了拓展,简单来说可以归纳如下:
左值使用的是对象的身份(即内存中的位置);
右值使用的是对象的值(内容);
int a = 1;
在上面代码的描述中,a和1都是表达式,不同的是,a是一个左值,1是一个右值,这个概念在实际上和C语言中的概念相同。
另外C++还具有另外一个重要的原则,在需要使用右值的地方可以使用左值来代替,但是不能把右值当作左值使用,在左值被当成右值使用时,实际上使用的是它的值。
int a = 1;//合法
int b = a;//合法
int 1 = b;//非法
需要注意的是,C++标准还规定任何变量都是左值。
int &&a = 1;
int &&b = a;//非法,a是一个左值
从对左值和右值的描述中可以看到,左值是一个持久的概念,需要手动去销毁左值,右值则是一个短暂的概念,在右值被使用完毕之后,这个对象就会被自动销毁。
在左值和右值的基础上,C++11派生出两个新的概念,左值引用和右值引用。
所谓的左值应用如下所示:
int i = 0;
int &a = i;
此时a就是一个左值引用,左值引用在之前的C++标准中就早已出现,并且一直在被使用,只是为了和新出现的右值引用的进行概念区分,才出现左值引用这个名词来对其进行描述。
右值引用如下所示:
int &&b = 10;
此时b就是一个右值引用,C++11语法规定,&用来描述左值引用,&&用来描述右值引用。
#使用右值引用
C++11中引入右值引用这个概念,实际上是为了完成一个操作,就是右值本身是一个临时对象,该对象将要被销毁,而右值引用可以绑定这个对象,然后对这个对象进行操作,获取这个对象中的状态。
通俗一点来说就是,右值引用可以去获取那些将要被销毁对象中的数据,对其进行利用,减少内存的频繁操作。
这个所谓的减少,本身还是依赖于类的相关实现,C++11为此特地引入了一个新的构造函数,移动构造函数,下面是一个简单的实现:
class A
{
...
A(A &&other)
{
cout << "class A move copy" << endl;
m_a = other.m_a;
m_str = other.m_str;
other.m_str = nullptr;
other.m_a = 0;
}
A &operator=(A &&other)
{
cout << "class = A copy" << endl;
m_a = other.m_a;
m_str = other.m_str;
other.m_a = 0;
other.m_str = nullptr;
return *this;
}
...
private:
int m_a;
char *m_str;
};
移动构造函数将会在右值传入的使用被调用,其行为是将传入的右值的数据完整的移动到新的对象中,然后将传入的右值中的变量进行初始化,等待销毁。
这样的操作,因为右值是一个即将要销毁的对象,所以这样可以在构建的时候,减少内存销毁和分配。
C++标准规定:
返回左值引用的函数,连同赋值、下表、解引用和前置递增/递减运算符都返回左值
返回非引用类型的函数,连同算术、关系、位置以及后置递增/递减运算符都生成右值
那么按照如上规定:下面这个对类A操作的函数将调用移动构造函数:
A returnA()
{
A a;
return a;
}
A newa = returnA();
!!!需要注意的是,gnu编译器会自动优化代码,导致没有出现相关的现象, 如果需要查看需要添加编译命令-fno-elide-constructors
。
在某些场景下,会出现需要将左值的参数转换成右值就行使用,而std::move就可以完成这个任务。
如下所示:
int a = 1;
int &&b = a;//非法,右值引用无法绑定左值
int &&c = std::move(a)//合法
这样就完成了左值到右值的转换处理,如果将其拓展到类上使用,那么就可以在参数是左值的情况下,完成调用移动构造函数的逻辑。
std::forward的语义也非常简单,很多资料上都将其称为完美转发,其实本质上std::forward针对的是之前提到的C++语法:所有变量都是左值。
这个语法导致了如果存在两个函数,都需要接受右值作为参数,但是其中一个函数调用了另外一个函数,那么就需要std::forward来解决,右值自动被转化为左值的问题。
void useA(A &a)
{
cout << "left" << endl;
}
void useA(A &&a)
{
cout << "rignt" << endl;
}
void useTotal(A &&a)
{
useA((a));
}
此时输出的结果为left
如果将useTotal函数就行如下修改:
void useTotal(A &&a)
{
useA(std::forward(a));
}
此时输出right。