Value Categories
https://oneraynyday.github.io/dev/2020/07/03/Value-Categories/
https://en.cppreference.com/w/cpp/language/value_category
History
C++98
C++98 中有 lvalue 和 rvalue 的概念,这个时候的概念很单纯
- lvalue intuitively describes an entity that is in storage somewhere, with a location. Basic scenarios includes
int a; // a is an lvalue
int* b; // *b is an lvalue
int c[10]; // c[0] is an lvalue
struct e { int x; }; // e.x is an lvalue
void f() {} // f is an lvalue
- rvalue is any expression that is not an lvalue
lvalue-to-rvalue conversion:在 a = b
执行过程中左值表达式 b 被”转化“为右值表达式,加载 b 的值到寄存器然后赋予 a 所在的地址。
Reference 被引入
Reference 被引入的原因一方面是为了支持更优越的操作符重载(因为传参类型不能是同类型的指针,如果不使用引用只能使用复制传参),另一方面是因为在不引入 Reference 的情况下有些操作符重载不可实现(需要对传入对象进行修改的操作)
struct integer {
integer(int v) : value(v) {}
int value;
};
// This doesn't actually modify i
void operator++(integer i) {
i.value++;
}
// This is ill-formed and won't compile -
// There is already an operator++ for integer*
// which moves the pointer itself.
void operator++(integer* i) {
*i.value++;
}
// This is our only option.
void operator++(integer& i) {
i.value++;
}
Reference 的局限
// bad! doesn't work for all situations!
// It works for:
// integer i1(2);
// integer i2(3);
// i1 + i2;
// integer(2) + integer(3) should yield integer(5)
// but these are rvalues, so it won't compile!
integer operator+(integer& i1, integer& i2) {
return integer(i1.value + i2.value);
}
// This is our only option.
integer operator+(const integer& i1, const integer& i2) {
return integer(i1.value + i2.value);
}
引入 const reference 的原因
- 避免重载两份相同功能的函数
- 减少函数实参传入右值时的重复构造,直接拿来用可以减少一次构造。
const reference 对右值传入参数的行为:The reason that only const integer&
works like this is because of temporary materialization which binds the temporary integer(2)
and integer(3)
to i1
and i2
respectively, and they will exist in memory somewhere.
const reference 对右值传参的行为与右值的定义相悖:The fact that sometimes rvalues could “materialize” and exist in storage in the limited scope of the function, and sometimes not have storage at all must be confusing. 但是 const reference 和 non-const reference 的行为差异非常地反直觉,解决方案是将 rvalue 拆分为 xvalue 和 prvalue:This is why C++ further split up the concept of rvalues and defined the materialized values as xvalues, for “expiring values” and no-storage values as prvalues, for “pure rvalues”.
C++11 引入 move semantics 进一步复杂化 value categories
移动语义引入的动机是简化大型数据的构造,原来只能分配新内存、复制一份新的然后删除旧的,现在可以直接把旧内存拿来使用。注意构造的次数是不变的,变的只是对传入右值引用的构造函数调用的复杂度。移动语义的窗口包括移动构造函数和移动赋值函数。
引入移动语义前:
- 传入 lvalue,调用复制构造函数
- 传入 prvalue,被转换为 xvalue,调用复制构造函数
引入移动语义后新增: - 传入 std::move(lvalue),被转换为 xvalue,调用移动构造函数,原 lvalue 处于无效状态
现在,移动后的左值属于什么 Value Categories,与 rvalue 拆分的命名规则不同,原来的 lvalue 变成了 glvalue,我们预想的 plvalue 变成了 lvalue。
注意各种值类型的定义的细节在不断改变
C++17
In C++17, copy ellision is now guaranteed for function calls returning prvalues, as in they never undergo temporary materialization.
RVO
General
https://oneraynyday.github.io/dev/2020/07/03/Value-Categories/
https://en.cppreference.com/w/cpp/language/value_category
Value Categories 谈论的对象是表达式。分类如下
Left Operand of the Assignment Operator
prvalue left operand no error stackoverflow
标准只限制了 builtin assignment operator 的 left operand 必须是 modifiable lvalue,在 Value Categories cppreference 给出了一个 prvalue 的反例,在 std::tie cppreference 中给出了有实际应用空间的例子。prvalue left operand no error stackoverflow 中清晰解释了普遍的 prvalue as left operand of assignment 并不一定是错误。目前了解到会报错的情况有
- An rvalue can't be used as the left-hand operand of the built-in assignment or compound assignment operators.
- the type of the prvalue is const-qualified.
- An rvalue cannot be used as the implicit object parameter of the lvalue ref-qualified implicit object member function