在C++11中所有的值一定属于左值、纯右值和将亡值三种值之一,分别介绍一下这三种类型。
左值与右值
在C++中定义左值与右值的比较标准的方法是根据其可以取地址来判断。左值就是可以对变量进行取地址或者有名字的变量,按照C语言中的规定也就是说其在内存中是被分配了位置;而右值就是不可以取地址、没有名字的。比如 a = b + c;其中a就是左值、b+c就是右值。
纯右值与将亡值
纯右值就是C++98中的右值的概念,就是指临时变量和一些不跟对象关联的值,比如1+2就是指临时变量,true和‘c’就是一些和对象不相关的值,还有函数的返回值和lamdba函数等
将亡值指的是与右值引用相关的表达式。比如:std::move()的返回值、类型为T&&的函数返回值等。
右值引用
上述提到了右值引用,右值引用指的是是右值的引用,由于右值一般不存在名字,可以通过引用的方式延长右值的生命周期。比如说如下的代码:
T && a = ReturnValue();
ReturnValue()
函数的返回值就是一种右值,通过利用其返回值将a进行初始化,这样就等价于”延长”了函数返回值的生命周期。
上述代码中也可以使用如下的方式进行初始化:
T a = ReturnValue();
这种方式相对于第一种方式多了一次对象的构造函数和析构函数的调用,如果返回值占用的内存比较大,这样可能会带来比较高的成本。
注意
- 非常量右值引用不可以绑定任何的左值
- 常量左值引用可以绑定非常量左值、常量左值和右值
- 非常量左值只可以绑定非常量
- 常量右值引用一般没有用处
std::move()
std::move()函数会强制将左值转化为右值引用,等价于一个类型转换的操作。一般使用这个方法的场景是在构造一个新对象的时候,尤其是这个新对象占用了很大的内存空间,将一个左值转化成为右值引用的时候,可以调用移动构造函数,这样可以降低对象创建带来的开销,如果此类中没有定义移动构造函数,那么就会调用这个类的拷贝构造函数。
移动语义
- 默认的移动构造函数与默认的拷贝构造函数相同,只是做按位拷贝的工作
- 在C++11中拷贝构造/赋值和移动构造/赋值函数必须同时提供或者同时不提供。如果自定义拷贝构造函数、拷贝赋值函数、移动赋值函数和析构函数中的一个或者多个,编译器不会生成默认的移动构造函数。如果自定义移动构造函数、移动赋值函数、拷贝赋值函数和析构函数中的一个或者多个,编译器不会生成默认的拷贝构造函数
- 对于只声明移动构造函数/赋值函数的类,通常数属于资源类型,比如:智能指针、文件流等
- RVO/NRVO表示返回值优化,如果使用参数-fno-elide-constructors这个参数将会关闭该优化;如果没有关闭该优化,对于如下代码:
T ReturnRvalue(){ A a(); return a;}
T b = ReturnValue();
其实b 对象所占用的空间就是a对象的空间。但是RVO/NRVO并不是总是有效的。
完美转发
之所有存在完美转发,其问题实质是:模板参数类型推导在转发过程中无法保证左右值的引用问题。而完美转发就是在不破坏const属性的前提下通过增加左右值引用概念和新增参数推导规则解决这个问题。
可以用如下代码来帮助理解完美转发:
template <typename T>
void IamForwarding(T t)
{
IrunCodeActually(t);
}
在上述代码中IamForwarding
会将参数转发给IrunCodeActually
函数来执行,但是在函数调用之间还会存在对象的拷贝,因此可以使用引用来解决该问题,但是又要考虑左值引用和右值引用的问题。可以使用重载来解决该问题,但是这样会造成代码的冗余。
最终解决这个问题的方法就是使用引用折叠,在引用折叠中规定:如果两个引用中有一个是左值引用,那么折叠的结果是一个左值引用。否则(即两个都是右值引用),折叠的结果是一个右值引用。,引用折叠规则如下:
与模板配合使用的例子
#include<iostream>
using namespace std;
void runcode(int && m)
{
cout << "rvalue ref" << endl;
}
void runcode(int & m)
{
cout << "lvalue ref" << endl;
}
void runcode(const int & m)
{
cout << "const lvalue ref" << endl;
}
void runcode(const int && m)
{
cout << "const rvalue ref" << endl;
}
template <typename T>
void PerfectForward(T &&t)
{
runcode(forward<T>(t));
}
int main()
{
int a;
int b;
const int c = 1;
const int d = 0;
PerfectForward(a);
PerfectForward(move(b));
PerfectForward(c);
PerfectForward(move(d));
}