for example:
Beta_ab&& Beta::toAB() const {
return move(Beta_ab(1, 1));
}
for example:
Beta_ab&& Beta::toAB() const {
return move(Beta_ab(1, 1));
}
Beta_ab&&
Beta::toAB() const {
return move(Beta_ab(1, 1));
}
This returns a dangling reference, just like with the lvalue reference case. After the function returns, the temporary object will get destructed. You should return Beta_ab
by value, like the following
Beta_ab
Beta::toAB() const {
return Beta_ab(1, 1);
}
Now, it's properly moving a temporary Beta_ab
object into the return value of the function. If the compiler can, it will avoid the move altogether, by using RVO (return value optimization). Now, you can do the following
Beta_ab ab = others.toAB();
And it will move construct the temporary into ab
, or do RVO to omit doing a move or copy altogether. I recommend you to read BoostCon09 Rvalue References 101 which explains the matter, and how (N)RVO happens to interact with this.
Your case of returning an rvalue reference would be a good idea in other occasions. Imagine you have a getAB()
function which you often invoke on a temporary. It's not optimal to make it return a const lvalue reference for rvalue temporaries. You may implement it like this
struct Beta {
Beta_ab ab;
Beta_ab const& getAB() const& { return ab; }
Beta_ab && getAB() && { return move(ab); }
};
Note that move
in this case is not optional, because ab
is neither a local automatic nor a temporary rvalue. Now, the ref-qualifier &&
says that the second function is invoked on rvalue temporaries, making the following move, instead of copy
Beta_ab ab = Beta().getAB();
return x;
is the same as return std::move(x)
. (where x
is a local variable (i.e. this point I've made doesn't directly apply to the original question about returning a temporary)). –
Unweave Beta_ab && Beta::toAB() const { return Beta_ab(1, 1); }
Is this correct? –
Seraphine int&& return_1() { int a{ 1 }; return move(a); // dangling reference? }
is working without problem. Do you have an example where it fails? –
Dunigan std::move
just a static_cast
to rvalue-reference? So if we removed the move
from Johannes' example and just said return ab;
the function would return an rvalue-reference to Beta_ab
initialized with ab
, that is, exactly the result of move(ab)
no? But in this case, the move
would be superfluous, contradicting Johannes' statement that "the move is not optional". –
Coastal It can be more efficient, for example, in a bit different context:
template <typename T>
T&& min_(T&& a, T &&b) {
return std::move(a < b? a: b);
}
int main() {
const std::string s = min_(std::string("A"), std::string("B"));
fprintf(stderr, "min: %s\n", s.c_str());
return 0;
}
As an interesting observation, on my machine clang++ -O3
generates 54 instructions for code above versus 62 instructions for regular std::min
. However, with -O0
it generates 518 instructions for code above versus 481 for regular std::min
.
UPDATE
For folks that disagree with or don't understand this answer, I would suggest to play with this sample in Compiler Explorer aka godbolt (https://godbolt.org) first before saying that something is wrong:
// use compiler flags: -O3 --std=c++20 -fno-exceptions
#include <utility>
#include <string>
// Example of the `min(a, b)` implementation that can take advantage of return by rvalue.
template <typename T>
T&& min_(T&& a, T &&b) {
return std::move(a < b? a: b);
}
// Test class that supports both copy and move construction.
struct Bar {
Bar();
Bar(const Bar &other);
Bar(Bar &&other);
~Bar();
char *ptr;
friend bool operator <(const Bar& lhs, const Bar& rhs);
};
// Just to tell the optimizer that we care about the value.
void consume(const void *);
#if 1
void with_rvalue() {
const Bar s = min_(Bar(), Bar());
consume(&s);
}
#else
void without_rvalue() {
const Bar s = std::min(Bar(), Bar());
consume(&s);
}
#endif
In with_rvalue()
we get:
Bar::Bar() [object constructor]
Bar::Bar() [object constructor]
operator<(Bar const&, Bar const&)
Bar::Bar(Bar&&) [move object constructor]
Bar::~Bar() [object destructor]
Bar::~Bar() [object destructor]
consume(void const*)
Bar::~Bar() [object destructor]
In without_rvalue()
we get:
Bar::Bar() [object constructor]
Bar::Bar() [object constructor]
operator<(Bar const&, Bar const&)
Bar::Bar(Bar const&) [copy object constructor]
Bar::~Bar() [object destructor]
Bar::~Bar() [object destructor]
consume(void const*)
Bar::~Bar() [object destructor]
So as you can see, when returning by rvalue we avoid an object copy. Practicality of this primitive is another topic, but here you have an example of how returning by rvalue generates more optimal code.
for(:)
and integrating over deallocated object. Fix: ideone.com/tQVOal –
Cephalic std::move()
is just used as an explicit cast to illustrate the point more clearly. It's not a code you would copy-paste into your project. It doesn't contradict top-voted answer, because there temporary object is created inside the function. Here returned object is one of the arguments (temporary objects are destroyed as the last step in evaluating the full-expression that (lexically) contains the point where they were created). –
Cephalic © 2022 - 2024 — McMap. All rights reserved.