生命周期
字面量
字面量(literal),顾名思义:字面上的量,是指直接写入代码的数值。使用二进制编辑器查看二进制文件时可以直接看到。
提示
字符串字面量是一个例外。当字符串字面量存在于类中时,它是一个右值,否则为左值
左值、右值
关于左值和右值的定义可以简要概括如下:
凡是可以被取地址的都是左值(充要条件)
凡是不可被取地址的都是右值(充要条件)
产出左值的表达式是左值表达式
产出右值的表达式是右值表达式
下面对左值和右值进一步分类: 1
左值( lvalue )指明(designates)了一个函数或对象。(假设 E 是一个函数指针,那么 *E 是一个左值)
将亡值( xvalue )指明了一个生命周期即将结束的对象(因此它可能被移动)。某些将亡值是由于右值参与运算的结果。
泛左值( glvalue )是一个左值或者将亡值
右值( rvalue )是将亡值或无名对象
纯右值( prvalue )是非将亡值的右值
- 1
ISO/IEC 14882:2011 chap. 3.10 (C++11 ISO标准文件)
左值
特征:
可取地址
左值指向了一个对象
左值具备泛左值的所有特征
以下表达式为左值:
变量、函数和数据成员
返回类型为左值引用的表达式
返回类型可被取地址的表达式。(例如
a = b、*p)非类中的字符串字面量
强制类型转换为左值引用的表达式。(例如
static_cast<int&>(x))返回类型为左值引用的表达式(例如强制类型转换、函数调用、转换为右值引用的函数指针)
对类对象的操作(例如
a[n]、a.m、p->m、p->*m,此时要求类对象为左值)对数组的操作(例如
a[n],此时要求数组为左值)
备注
什么是转换为右值引用的函数指针?
static_cast<void (&&)(int) >(x);
小技巧
由于
std::endl、std::cout等也是对象,可以被取地址,因此它们也是左值表达式。“位于表达式的左边” 在C++11 的值分类不是左值的必要条件,例如
a = b = c,b = c位于表达式的右边
纯右值
特征:
纯右值不可能被取地址
纯右值没有多态
非类非数组类型的纯右值不能被cv限定(const-volatile qualifiers)[#]_
纯右值具备右值的所有特征
纯右值不能是不完整的类型 2 (void类型除外)
纯右值不能包含抽象类或数组
以下表达式为右值表达式
非字符串字面量的字面量
返回类型不是引用的函数调用(例如
std::string getName())所有返回临时对象的表达式(算数运算符、逻辑运算符、&a)
- 所有无法取地址的对象(如
this指针、lambda表达式) a.m 。 其中m是成员枚举或非静态成员函数,或a是左值, m既不是引用类型也不是静态成员
a->m 。其中m是成员枚举或者非静态成员函数
a.*mp。 其中mp是指向成员函数的指针或者a是一个右值,mp是指向成员变量的指针
- 所有无法取地址的对象(如
枚举
重要
对于涉及到成员的表达式:
a.m、p->m、a.*mp、p->*mp,返回的类型即可能为左值也可能为纯右值。请注意:
++a是左值,而a++是右值。你可以尝试自己创建一个类,重载前置++和后置++运算符来发现其中的magic。
将亡值
特征:
将亡值同时具备右值引用和泛左值的特征,尤其是将亡值可以像右值那样绑定到右值引用,又可以像泛左值那样具备多态。非类的将亡值可以被cv限定。
以下为将亡值表达式
返回类型为右值引用的函数调用(如
std::move(x))从右值对象获取的属性(例如
a[m]、a.*mp,此时a为右值)转换对象为右值引用的表达式(例如
static_cast<char&&>(x))
泛左值
泛左值要么是将亡值,要么是左值
特征:
右值
右值要么是将亡值,要么是纯右值
右值无法被取地址
右值可以被用于初始化
被const限定的左值引用,此时右值的生命周期将被延长至与左值相同一个右值可以用于初始化右值引用,此时其生命周期将被延长至与右值引用相同
在选择重载函数时,右值将倾向于右值引用,而不是
被const限定的左值引用。类中的字符串字面量
悬空引用 3
当引用指向对象的生命周期结束时,对引用的访问是未定义行为:
std::string& f()
{
std::string s = "Example";
return s; // 退出 s 的作用域:
// 调用其析构函数并解分配其存储
}
std::string& r = f(); // 悬垂引用
std::cout << r; // 未定义行为:从悬垂引用读取
std::string s = f(); // 未定义行为:从悬垂引用复制初始化
- 4