3 ms·
Another way to frame this discussion would be in terms of xvalues. The standard defines an xvalue as an "eXpiring value", which is value near the end of it's li
by mavam 11y ago
Another way to frame this discussion would be in terms of xvalues. The standard defines an xvalue as an "eXpiring value", which is value near the end of it's lifetime [1,2].
struct X {};
void f() {
X x;
return x; // elided
}
void f(X x) {
return x; // elided
}
Perhaps not as intuitive as the first copy elision is the second, but accepting a parameter by value and returning it also enables copy elision because it is an xvalue at the end of the scope. A move is equally detrimental to performance in both cases.
[1] http://eel.is/c++draft/basic.lval#xvalue http://eel.is/c++draft/basic.lval#xvalue
[2] http://stackoverflow.com/q/3601602/1170277 http://stackoverflow.com/q/3601602/1170277
- kentonv 11y ago> accepting a parameter by value and returning it also enables copy elision No it doesn't. Parameters are constructed by the caller (in order to allow eliding the parameter copy, if the argument is a temporary anyway), therefore returning a parameter cannot be elided because that would have required the caller to know to construct the parameter in the return value slot. If you actually test your code, you'll see that in the `f(X x)` case, the return is not elided. With that said, in C++11, the return is automatically by move, not by copy. Any "return x;" where x is a local variable (including parameters) will be by move. But prior to C++11, it would have been a copy. This is IMO the biggest problem with relying on RVO for anything: people rarely understand all of the rules around it, and may reasonably assume that RVO will kick in in cases where it doesn't. It's even harder if you have a function with a few branches and multiple returns -- in some cases, even when the branching happens before the return variable is declared, the compiler will fail to RVO it. Luckily C++11's "returning a local variable is automatically by move" rule covers you in most such cases. FWIW, I highly recommend following Rust's rule: Only trivial, shallow types can be implicitly copied (i.e. only cases where the copy is a simple memcpy()). Anything else can only be moved, but is free to define a `clone()` method for explicit copies. If your type doesn't have a copy constructor then you can't accidentally call it. :)
- mavam 11y ago> No it doesn't. Parameters are constructed by the caller (in order to allow eliding the parameter copy, if the argument is a temporary anyway), therefore returning a parameter cannot be elided because that would have required the caller to know to construct the parameter in the return value slot. Sorry, I confounded two things here. What I meant to say is that "x" is an xvalue in both scenarios and you wouldn't write std::move(x) in either case. For the overload f(X) you'd always incur a move, whereas for the overload f() you'd always get a copy elision. Concretely, I've looked at the assembly of this code: #include <string> struct X { std::string str; }; auto f(X x) -> X { return x; } auto f() -> X { X x; return x; } auto main() -> int { X x = f(); auto y = f(x); } When compiling with c++ -g -std=c++14 test.cc && otool -tV a.out | c++filt the assembly shows that indeed shows that f(X) invokes the move constructor, whereas the copy is elided for f().