原来我也只知道C++中有左值和右值,通过今天的学习才知道,C++中不光有左值和右值。
C++中有5种值类别,lvalue
, rvalue
, glvalue
, xvalue
, prvalue
- lvalue: 左值
- rvalue: 右值
- glvalue: generalized lvalue, 广义左值
- xvalue: expiring lvalue, 将亡值
- prvalue: prue rvalue, 纯右值
看到这些不要害怕,我们一点点来。
首先我们看下最熟悉的lvalue.
lvalue
左值是有标识符,可以取地址的表达式。 比如说:
-
变量,函数名,类的数据成员名
这些都是变量名嘛
-
返回左值引用的表达式,如 ++x, x = 1, cout << " "
-
字符串字面值,如"hello world"
值得注意的是,&
, reference 其实是左值引用,我们在调用函数时,传递的参数是左值,编译器就会匹配函数参数是左值的那一个函数。
一个常量只能绑定到常左值引用,比如:
const int a = 1;
// int& b = a; // 编译不过
const int& c = a;
prvalue
纯右值,是没有标识符,不可取地址的表达式,一般称之为临时对象
- 非引用类型的表达式,如x++, x+1, make_shared(42)
- 除字符串字面值之外的字面值,如 42, true
在C++11之前,右值可以绑定到常左值引用的参数,但是不可以绑定到非 常左值引用。 如:
// int& a = 1; // 编译不过
const int& a = 1;
在C++11,引入了右值引用,&&
。这时我们就可以右值绑定到常右值引用,或者是非常右值引用了。
int&& a = 1;
const int&& b = 1;
引入一种额外的引用类型当然增加了语言的复杂性,但也带来了很多优化的可能性。由于 C++ 有重载,我们就可以根据不同的引用类型,来选择不同的重载函数,来完成不同的行为。
void f(int& a) {
cout << "int&" << endl;
}
void f(int&& a) {
cout << "int&&" << endl;
}
int main(int argc, char const* argv[])
{
int a = 1;
f(a); // a 是一个左值,变量名
f(1); // 数字1是一个右值
return 0;
}
输出:
int&
int&&
那f内的a是左值还是右值呢? 都是左值,因为他们都是一个变量名。类型右值引用的变量是一个左值
xvalue
将亡值,也是一个右值
标准库里有一个move函数,std::move
,它的作用是把一个左值引用强制转换成一个右值引用,并不改变其内容。
我们可以把move的返回值看成一个有名字的右值,为了跟无名的纯右值相区别,C++就把这种表达式叫做xvalue,xvalue也是不能取地址的。
插播一个生命周期
一个变量的生命周期在超出作用域时结束。如果一个变量代表一个对象,当然这个对象的生命周期也在那时结束。
那临时对象(prvalue)呢?
C++ 的规则是:一个临时对象会在包含这个临时对象的完整表达式估值完成后、按生成顺序的逆序被销毁,除非有生命周期延长发生。
我们先看一个没有生命周期延长的基本情况:
class shape {
public:
virtual ~shape() {
}
};
class circle : public shape {
public: