RVO (Return Value Optimization) and NRVO (Named Return Value Optimization) (also called copy elision)
some small questions
- Q: order of local variable desctruction and returning value copy/move
A: return statement - Q: will right-reference value return to caller when call move ctor in return statement?
definition of RVO
in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value
example
detail introduction
RVO is a optimization techonology that effects constrcution behavior and changes stack’s return value layout.
Here is a blog about this.
Before RVO, function stack’s layout is like below.
With RVO, function stack’s layout is like below.
move semantics and rvalue
Question
- Why do we need move semantics since we have RVO?
- How does move semantics effect code behavior?
lvaue and rvalue
lvaue
is short for left-reference value and rvalue
is for right-reference.
Here is an example.
1 | auto i = std::string{"value categories"}; |
- must be aware that rvalue is always a compile time concept.
- rvalue is a un-named value, cpp only have named variables, thus it means that each memory can be reached and destructed correctly.
- cv rvalue can’t move to a value directly. Its behavior will be same as normal cv lvaue after compiling.
foo(source_const_rvalue_ref()); // #1
detail binding rules
1 | struct A {}; |
lvalues, both const and non-const bind to foo(const A&). Named references, both lvalue and rvalue, all bind to foo(const A&). Non-const rvalues and unnamed rvalue references bind to foo(A&&). Const rvalues and const rvalue references bind to foo(const A&). Had there been a foo(const A&&) available, they would have bound to that. The lvalue references bind to foo(const A&).
Caution
move will become a normal rvalue or copy with bind it with a const name.
std::move
what is std::move
?
std::move
is just a type cast annotation in compile time and exactly has no effect in runtime. (including std::forward
)std::move
casts an object to a rvalue. It has no other effect.
- If left value is only a reference, rvalue will be bind to it.
- If left named value is not a reference and has move ctor, compile will make it call move-ctor.
- If doesn’t have a move-ctor(be deleted), copy-ctor will happen.
Move doesn’t move anything!
1 | /** |
move ctor
Question
- is move behavior always safe and don’t need us to pay any more attention on it?
move ctor syntax
1 | class A { |
use move ctor
be patient that cpp’s move semantics is non-destructive
- Rust move semantics is destructive
- Cpp is value passing based language but Rust is move passing based
- Comparing with Rust‘s variable lifetime and Cpp’s, you will have a deeper understanding about cpp’s RAII and move semantics
how to write a clean move-ctor? example
remind that you must clean stealed object’s resource. On the other hand, any object could be moved can be safely resource-less.
some potential problems with move-ctor
when to disable move-ctor? std::enable_shared_from_this
Some small examples
move semantcis
Question
- Is move is as effective as we expect?
- Compare move with RVO?
SSO (small string optimization)
small strings will use stack space rather than heap space.
move doesn’t reduce cost and remove RVO optimization.
Some more questions
- Before you see cpp’s move semantics, what should a
move
look like in your mind? - Do cpp move semantics has runtime cost?
Some personal opinions
- Currently programming language can be divided into several categories:
- pass by value
- pass by reference (mostly are reference pointer copy and its behavior is pass-by-value. real pass-by-reference is not a sugar of pointer copy, it should be just alias during compile time. Thus, it will cause confliction between original value’s and alias’ lifetime)
- pass by move
- RVO is real move and rvalue is just a simulation. It’s not real zero-cost.
- do not use std::move just for an intuition when you return. It’s final behavior is more complex than expected.
Universal reference
introduction
Universal reference can match any kind of input argument.
Here is a samle.
1 | template <typename T> |
perfect forward
Question: Why we need perfect forward?
Here is a example of boost::compressed_pair. By the way, it’s a very good example for template SFINAE
.
1 | compressed_pair<T1, T2>::compressed_pair(compressed_pair&& x) |
This just solve reference and right-reference without cv
constraint.
If we want to both support const
, N-argument case would require 2N overloads.
So be aware that perfect forward does nothing in runtime. It just forwards arguments with original type to next function.
It looks like below.
1 | void foo(Arg1&, Arg2&&) |
implementation
1 | /** |
when should use it
Perfect forward and universal reference are all only for generics.
When you have multiple implements(count can be fixed or not) and need an universal entry, you can use it.
Let’s use std::make_unique
as an example.
1 | template<typename _Tp> |
Some interesting questions
- what is reference’s semantics? (don’t bind to a specfic language)
- do reference in cpp really zero-cost?
- why does cpp need reference semantics and c does not?
- why previous cpp doesn’t allow reference of a reference?
- what’s a dangling reference?
- what is a real move semantics in your mind? (not bind to any language)
- how should behavior be when move between stack and heap?
- when can a move action be absolutely safe?
- which pass-by type do you think for c, cpp, java, go, python, perl .etc?
move behavior in Rust
Move constructors are meaningless in Rust because we don’t enable types to “care” about their location in memory. Every type must be ready for it to be blindly memcopied to somewhere else in memory. This means pure on-the-stack-but- still-movable intrusive linked lists are simply not happening in Rust (safely).
Here is document link.
references
https://reuk.github.io/page-fault/emcpp/chapter_5.html
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm
https://community.ibm.com/community/user/power/blogs/archive-user/2020/05/28/rvo-vs-stdmove
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm
https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers