左值、右值通常不是通过一个严谨的定义而为人所知的,大多数时候左右值的定义与其判别方法是一体的。一个最为典型的判别方法就是,在赋值表达式中,出现在等号左边的就是“左值”,而在等号右边的,则称为“右值”。
更为细致地,在C++11中,右值是由两个概念构成的,一个是将亡值(xvalue,expiring Value),另一个则是纯右值(prvalue,Pure Rvalue)。
纯右值是C++98标准中右值的概念,用于辨识临时变量和一些不跟对象关联的值。
将亡值则是C++11新增的跟右值引用相关的表达式,通常是将要被移动的对象
T && a = ReturnRvalue ();
ReturnRvalue
函数返回的右值在表达式语句结束后,其生命也就终结了,而通过右值引用的声明,该右值又"重获新生",其生命期将与右值引用类型变量 a的生命期一样。只要 a还"活着",该右值临时量将会一直"存活"下去。
相比于以下语句的声明方式∶
T b = ReturnRvalue();
通过右值引用的方式,少一次对象的析构以及一次对象的构造。a是右值引用直接绑定ReturnRvalue
的返回值,b由临时值够成,临时值会经历一次构造和析构。
右值引用是不能够绑定到任何的左值的
C++98 中 左值引用不能绑定右值,常量左值引用可以接受左值、常量左值、右值初始化
C++ 中,并不是所有情况下 && 都代表是一个右值引用,具体的场景体现在模板和自动类型推导中
在函数模板中使用 && 需要通过传入的实参来确定参数 param 的实际类型。
template<typename T>
void f(T&& param);
void f1(const T&& param);
//对于 f(10) 来说传入的实参 10 是右值,因此 T&& 表示右值引用
f(10);
int x = 10;
//对于 f(x) 来说传入的实参是 x 是左值,因此 T&& 表示左值引用
f(x);
//f1(x) 的参数是 const T&& 不是未定引用类型,不需要推导,本身就表示一个右值引用
f1(x);
如果是模板参数,需要指定为 T&&,如果是自动类型推导,需要指定为 auto &&,在这两种场景下 && 被称作未定的引用类型。
int main()
{
int x = 520, y = 1314;
//auto&& 表示一个整形的左值引用
auto&& v1 = x;
//auto&& 表示一个整形的右值引用
auto&& v2 = 250;
//decltype(x)&& 等价于 int&& 是一个右值引用不是未定引用类型,y 是一个左值,不能使用左值初始化一个右值引用类型。
decltype(x)&& v3 = y; // error
cout << "v1: " << v1 << ", v2: " << v2 << endl;
return 0;
};
在 C++11中,拷贝/移动构造函数实际上有以下3个版本∶
T Object(T &)
T Object(const T &)
T Object(T &&)
完美转发(perfect forwarding),是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。
template <typename T>
void IamForwording (T t) { IrunCodeActually(t); }
IrunCodeActually
是执行代码的目标函数,希望转发函数将参数按照传入 lamforwarding时的类型传递。传入lamForwording
的是右值对象,IrunCodeActually
就能获得右值对象)。
在上面例子中,使用了最基本类型进行转发,该方法会导致参数在传给 IrunCodeActually 之前就产生了一次额外的临时对象拷贝。因此这样的转发只能说是正确的转发,但谈不上完美。
C++11是如何解决完美转发的问题
C++11是通过引入一条所谓**"引用折叠”(reference collapsing)** 的新语言规则,并结合新的模板推导规则来完成完美转发
typedef const int T;
typedef T& TR;
TR& v = 1; // 该声明在C++98中会导致编译错误
在C++11中,一旦出现了这样的表达式,就会发生引用折叠,即将复杂的未知表达式折叠为已知的简单表达式
一旦定义中出现了左值引用,引用折叠总是优先将其折叠为左值引用
转发函数的实参是类型X的一个左值引用,那么模板参数被推导为X&类型
转发函数的实参是类型X的一个右值引用,那么模板参数被推导为X&&类型
template <typename T>
void IamForwording(T && t) {
IruncodeActually(static_cast<T &&>(t));
}
这里的static_cast是留给传递右值用的
void IamForwording(X& && t) {
IruncodeActually(static_cast<X& &&>(t));
}
//应用上引用折叠规则,就是 -->>
void IamForwording(X& t) {
IrunCodeActually(static_cast<X&>(t));
}
调用转发函数时传入了一个X类型的右值引用的话,我们的转发函数将被实例化为:
void IamForwording(X&& && t) {
IrunCodeActually(static_cast<X&& &&>(t));
}
//应用上引用折叠规则,就是 -->>
void IamForwording(X&& t) {
IrunCodeActually(static_cast<X&&>(t));
}
static_cast的重要性: 将传入参数转换为右值。std∶move通常就是一个static_cast。
在C++11中,用于完美转发的函数叫做 forward
template <typename T>
void IamForwording(T && t) {
IrunCodeActually(forward(t));
}
【实现完美转发的例子】
#include
using namespace std;
template <typename T,typename U>
void PerfectForward (T &&t,U& Func){
cout << t <<"\tforwarded..."<< endl;
Func (forward<T>(t));
}
void RunCode (double && m) {}
void RunHome (double && h) {}
void RunComp (double && c) {}
int main(){
PerfectForward(1.5,RunComp); //1.5 forwarded...
PerfectForward(8,RunCode);// 8 forwarded...
PerfectForward (1.5,RunHome);// 1.5 forwarded...
}