问题
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