价值与对象

Value vs. Object

本文关键字:对象      更新时间:2023-10-16

[C++17]

当评估prvalue表达式时,标准会说它会产生一个值。在5的情况下,表达式是一个prvalue,其计算结果为值5

但是,当您有一个prvalue时,它主要是一个对象的初始值设定项,例如Foo{}。这个表达式的值是多少?结果会是由prvalue到xvalue转换创建的临时对象吗?这就引出了我更广泛的问题,即值和对象之间的差异

[interro.object]/1:

当隐式更改联合的活动成员时,或当创建临时对象时,对象由定义、新表达式创建一个物体在其建造期、使用寿命和破坏期都占据了一个存储区域

无论prvalue是否具有类似Foo{}的类类型,作为文字5,都被视为一个值,如果确实需要,则该值将用于初始化对象,此时该值将具体化为对象。

[class.temporary]/2:

为了避免创建不必要的临时物体。

在同一部分下,您会发现一个列表,描述临时性何时具体化

是一个抽象概念。一个值与表征或标识该值的一组实现相关联。例如,价值10美元的人可以买一本书或一顿饭。

一个值可以有多个表示形式。例如,10美元可以用硬币表示,也可以作为比特存储在银行账户中。

对象对于就像银行账户货币量一样:对象(/银行账户)持有该值的表示(/10$)。这在【basic.types】:中进行了描述

类型T的对象value表示是参与表示类型T的值的比特集。对象表示中不属于值表示的位是填充位。

然后在[interro.object]中指定一个对象与存储区域相关联:

一个对象在其构建期([class.cdtor])、整个生命周期和销毁期([cclass.cdtor】)中占据存储区域

如果我们考虑一个抽象机器,它有一个执行操作的中央处理器单元和一个可以存储对象(保存值表示)的分离存储器,那么对象及其值之间的区别就更合理了。当对某个值执行操作时,该值会加载到不同的cpu寄存器中。因此,cpu中的值没有相同的表示形式:一个连续的比特序列,就像它在对象中一样。此外,任何cpu都可以自由地表示寄存器中最适合其需要的值。

当cpu执行一个操作时,它对存储在寄存器中的一段值执行操作。执行该操作后,cpu可以将结果保存在对象内部的内存中,或者继续对该值进行操作。

在对值的操作以及从对象到对象的存储或加载中,操作的分解出现在标准中:

  • 加载左值到右值的转换[conv.lvalue]非函数、非数组类型T的glvalue可以转换为prvalue

  • c++中的所有操作都将产生一系列具有内置含义的基本运算。这些操作大多适用于值(prvalue),而不适用于对象。在执行这些操作之前,应用左值到右值[expr]每当glvalue表达式显示为期望该操作数为prvalue的运算符的操作数时,左值到右值,[…]

  • 这些对值进行操作的内置操作的结果总是prvalue(prvalue只是一个不与任何对象关联的值)。然后,结果值可以用作其他内置操作的操作数,或者初始化对象(机器内存中的存储操作),[basic.lval]:prvalue是一个表达式,其求值初始化对象或位字段,或计算运算符的操作数值,具体由其出现的上下文指定

为了说明这一点,让我们分析一下这段简单的代码:

int main(int argc, char* argv[]){
int j = 2*argc+1;
}
  1. 2*argc内置运算符*由两个参数2argc调用。argc是一个左值,因此应用左值到右值argc的值被加载到cpu寄存器中(2可以是立即数),并且执行multiply操作
  2. 2*argc的结果是直接用作第一操作数(2*argc)+(argc)的prvalue。然后,最后一次操作的结果prvalue用于初始化对象j:结果值被存储在j的内存表示中

值是一个概念;物体是一个有生命的东西。对于具有复杂构造函数的类类型,这种区别往往要重要得多,但规则同样适用于所有类型。

考虑一下这个简单的程序:

std::string foo() { return std::string{"Hello"}; }
int main() {
std::string f = foo();
}

foo不创建对象。创建一个对象需要调用类的构造函数来开始对象的生存期。对于std::string,这可能涉及到分配内存和复制字符,出于显而易见的原因,我们希望避免这样做太多次。

相反,foo返回一个值。它返回"用字符"Hello"初始化的字符串"的概念。最终,main能够采用抽象概念并构造一个对象来表示该值。由于这种区别,只创建了一个对象,因此开始和结束对象生命周期的额外成本只需要支付一次。