问题
Consider the following:
struct A { /* ... */ };
A foo() {
auto p = std::make_pair(A{}, 2);
// ... do something
return p.first;
}
auto a = foo();
Will p.first
be copied, moved or RVO-ed?
回答1:
I've found in Visual Studio 2010 and in gcc-5.1 RVO is not applied (see for example http://coliru.stacked-crooked.com/a/17666dd9e532da76).
The relevant section of the standard is 12.8.31.1 [class.copy]. It states that copy elision is permitted (my highlighting):
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 parameter or a variable introduced by the exception-declaration of a handler ([except.handle])) with the same type (ignoring cv-qualification) as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function's return value
Since p.first
is not the name of an object, RVO is prohibited.
回答2:
Just to add a little more fuel, how would this function if RVO were in play? The caller has put an instance of A
somewhere in memory and then calls foo
to assign to it (even better, let's assume that that A
was a part of a larger struct, and let's assume that it is correctly aligned such that the next member of the struct is immediately after that instance of A
). Assuming RVO were in play, the first
portion of p
is located where the caller wanted it, but where does the int
that is second
get placed? It has to go right after the instance of A
in order to keep the pair
functioning correctly, but at the source location, there's some other member right after that instance of A
.
I would expect that RVO would not be happening in this place as you are only returning a portion of a larger object. A move could happen as first
would have to be left in a destructible state.
回答3:
@atkins got here first with the answer. Just adding this little test program which you may find useful in future when tracking move/assign behaviour.
#include <iostream>
#include <string>
using namespace std::string_literals;
struct A {
A()
: history("created")
{
}
A(A&& r)
: history("move-constructed,"s + r.history)
{
r.history = "zombie: was "s + r.history;
}
A(const A& r)
: history("copied from: " + r.history)
{
}
~A() {
history = "destroyed,"s + history;
std::cout << history << std::endl;
}
A& operator=(A&& r) {
history = "move-assigned from " + r.history + " (was "s + history + ")"s;
r.history = "zombie: was "s + r.history;
return *this;
}
A& operator=(const A&r ) {
history = "copied from " + r.history;
return *this;
}
std::string history;
};
A foo() {
auto p = std::make_pair(A{}, 2);
// ... do something
return p.first;
}
auto main() -> int
{
auto a = foo();
return 0;
}
example output:
destroyed,zombie: was created
destroyed,move-constructed,created
destroyed,copied from: move-constructed,created
回答4:
Consider following code:
struct A {};
struct B {};
struct C { B c[100000]; };
A callee()
{
struct S
{
A a;
C c;
} s;
return s.a;
}
void caller()
{
A a = callee();
// here should lie free unused spacer of size B[100000]
B b;
}
"Partial" RVO should result in excessive stack usage bloating in caller, because (I think) S
can be constructed only entirely in caller stack frame.
Another issue is ~S()
behaviour:
// a.hpp
struct A {};
struct B {};
struct C { A a; B b; ~C(); };
// a.cpp
#include "a.hpp"
~C() { /* ... */; }
// main.cpp
#include "a.hpp"
A callee()
{
C c;
return c.a;
} // How to destruct c partially, having the user defined ~C() in another TU?
// Even if destructor is inline and its body is visible,
// how to automatically change its logic properly?
// It is impossible in general case.
void caller() { A a = callee(); }
来源:https://stackoverflow.com/questions/33344259/does-rvo-work-on-object-members