prvalue 不是临时量。
A prvalue is not a temporary

原始链接: https://blog.knatten.org/2025/10/31/a-prvalue-is-not-a-temporary/

## 理解 C++ 中的 prvalue 与临时对象 本文澄清了一个常见的误解:prvalue(纯右值)不*一定*是临时对象。在 C++ 中,表达式被分为左值(可识别的内存位置)或右值(可以被移动的表达式)。右值进一步分为 xvalue(即将失效的左值,例如使用 `std::move` 创建的)和 prvalue——代表对象的*概念*,而非对象本身。 重要的是,像 `std::vector{1,2,3}` 这样的 prvalue 不会自动成为临时对象。相反,它们可以直接初始化变量或函数参数,*无需*创建拷贝或移动。这发生在按值传递或直接初始化时。 然而,如果需要一个引用来绑定到它,例如传递给接受 `const std::vector&` 参数的函数,prvalue *会* 转化为临时对象。 类似地,按值返回的函数会创建一个 prvalue,直接初始化接收变量。 关键在于,prvalue 代表一个概念对象,避免了不必要的临时对象创建和潜在的性能开销。这并非关于优化*移除*临时对象,而是关于*避免其创建*。

这个Hacker News讨论围绕一篇博客文章([https://knatten.org/2018/03/09/lvalues-rvalues-glvalues/](https://knatten.org/2018/03/09/lvalues-rvalues-glvalues/)),详细介绍了C++值范畴的细微差别——左值、右值、广值、纯右值和万能值。 核心问题是C++的复杂性,特别是像“移动语义”这样的概念,通常解释得不够清楚。 许多评论者分享了难以理解这些概念的经历,其中一人指出需要重新学习C才能开始学习C++。 另一些人指出术语(“从…移动”)可能令人困惑,因为它与日常英语用法不符,并且需要深入理解底层机制。 讨论澄清了C++中的“移动”指的是利用右值引用和移动构造函数来有效地将资源从一个对象转移到另一个对象,避免代价高昂的复制。 虽然该特性有利于性能,但它也为语言及其程序员思维模型增加了显著的复杂性。
## Rust vs. C++ 值类别 - 总结 这次Hacker News讨论围绕一篇博客文章([https://blog.knatten.org/2018/03/09/lvalues-rvalues-glvalues/](https://blog.knatten.org/2018/03/09/lvalues-rvalues-glvalues/))展开,比较了Rust和C++中的值类别。核心论点是Rust简化了C++的一个复杂方面。 C++具有三种主要值类别(prvalue, xvalue, lvalue)以及glvalue和rvalue,而Rust仅使用两种:一个位置(类似于glvalue)和一个值(类似于prvalue)。虽然两种语言都实现了类似的结果,但C++的额外类别增加了复杂性。 对话涉及C++中的移动语义,Rust中对类似概念的潜在需求(特别是关于堆初始化),以及围绕这些类别经常令人困惑的术语。用户争论Rust的简化是否以隐藏底层复杂性为代价,并强调理解这些概念对于高效C++编程的重要性。一个关键点是,C++中的“移动”指的是传递到移动构造函数/赋值运算符,而不是移动行为本身。 最后,讨论指出原始文章在被Y Combinator的排名系统暂时隐藏后,最近被重新提交。
相关文章

原文

This is part one of a series of blog posts on temporaries, copies, return value optimization, and passing by value vs. reference.

A good place to start, and the point of this first article, is how a prvalue isn’t necessarily a temporary.

If you’re entirely new to value categories (lvalues, rvalues etc.), you might want to read lvalues, rvalues, glvalues, prvalues, xvalues, help! first.

lvalues vs rvalues

An lvalue is an expression that can not be moved from. An rvalue is an expression that can be moved from.

Let’s first have a look at lvalues. Given this variable v:

std::vector<int> v{1,2,3};

If I now write the expression v somewhere, v is referring to an actual variable. I can’t just move from it, as it would mess up an existing object that someone else could still be using. We call an expression like this an lvalue.

For instance, if I pass my existing vector to a function useVector:

Here, the expression v is an lvalue, and useVector can’t move from it. After all, someone might want to keep using v on a following line.

But if I know I won’t be needing v anymore, I can turn it into an rvalue, by wrapping it in std::move:

Here, the expression std::move(v) is an rvalue. useVector would now be allowed to move from v. (And I must take care to not use v again, since it might have been moved into useVector.)

rvalues: xvalues vs prvalues

Here’s another way to get an rvalue:

useVector(std::vector{1,2,3});

Here, the expression std::vector{1,2,3} is also an rvalue, and again useVector would be allowed to move from it.

Notice, however, that these are two different types of rvalues. std::move(v) takes an existing object and casts it to an rvalue. That type of rvalue is called an xvalue, or “eXpiring lvalue”.

On the other hand, std::vector{1,2,3} is a prvalue, or “pure rvalue”. Unlike an xvalue, this expression never referred to an existing object in the first place. People sometimes call this “a temporary”, but, as is the main point of this article, that’s not necessarily true.

A prvalue in itself is not a temporary. A prvalue is not an object. A prvalue just represents “the idea of the object”, and only turns into a temporary when it absolutely needs to. For instance:

std::vector v = std::vector{1,2,3};

Here, the prvalue std::vector{1,2,3} does not turn into a temporary that is then used to initialize v. Rather, the prvalue is used to initialize v directly, just as if you’d written std::vector v{1,2,3};. No extra temporary is created, and no copies or moves are performed.

Similarly:

void useVector(std::vector<int> v);

useVector(std::vector{1,2,3});

Here, useVector takes its parameter by value. std::vector{1,2,3} never turns into a temporary, the prvalue expression is instead used to initialize the parameter v directly, just like in the previous example. No extra temporary is created, and no copies of moves are performed.

Temporary materialization

However, if useVector takes its parameter by reference:

void useVector(const std::vector<int>& v);

useVector(std::vector{1,2,3});

Now, the reference parameter v needs some object to bind to, and std::vector{1,2,3} actually turns into a temporary object that v can bind to. This is called “temporary materialization”.

Return values

A function call that returns by value is also a prvalue 1. So, given this definition of getVector() and a call to it:

std::vector<int> getVector();

std::vector<int> v = getVector();

Here, the call getVector() is a prvalue which initializes v directly. There is no temporary that is then copied/moved into v. (There might be a copy involved in the return statement inside getVector(), but that’s a story for the next article.)

Conclusion

The point is that a prvalue only materializes into a temporary as a very last resort, avoiding unnecessary copies or moves. Until it needs to materialize, it only represents “the idea of the object”, i.e. what the object would be when it materializes into a temporary or is used to initialize something.

It is important to note that this has nothing to do with optimization. There is no temporary std::vector to optimize away in the first place, there’s just the prvalue, just the “idea of the object”. And then that idea of an object can materialize into an actual object if needed.

Footnotes


Discover more from C++ on a Friday

Subscribe to get the latest posts sent to your email.

联系我们 contact @ memedata.com