6 ms·
Oh boy there are some serious errors here. Returning a stack allocated value does not invoke the move constructor. It doesn't copy either. This is a separate o
by banachtarski 10y ago
Oh boy there are some serious errors here.
Returning a stack allocated value does not invoke the move constructor. It doesn't copy either. This is a separate optimization that existed even prior to C++11 called the "named return value optimization" or NRVO for short. And by "sufficient new compiler" you really should just say every compiler. No compiler I know of in existence used by actual people doesn't implement this.
Here's a demo: http://ideone.com/QRqj5P http://ideone.com/QRqj5P
It's VERY important that this optimization isn't confused with move construction as the latter would actually be extremely less performant than what actually happens.
- Manishearth 10y agoI think this is due to a confusion between the two kinds of "move" in the article. Returning a value from a C++ function is a "move" in the Rust sense. It's not a move in the C++ sense, since C++ moves involve move constructors. It's probably this confusion that led to it being written that way, probably should be fixed.
- banachtarski 10y agoHe has a line where he writes return val; // val is `rvalue` here This is the part that isn't accurate. "val" is a named reference and is not an rvalue. There isn't any "moving" up the stack that happens. He's likely confusing this with an expiring value (a subtype of rvalue) that happens in code like foo(returns_something()); The returned value from returns_something() is un-named and is about to die after this line executes (lives until foo's invocation unwinds) and so it is an rvalue. You can't have a named rvalue unless you std::move explicitly I don't think.
- StephanTLavavej 10y agoIt's a little more subtle than that. "val" is definitely an lvalue. However, when returning a local variable like this, C++11 has a rule: "if the NRVO activates, there are no copies/moves. Otherwise, see if you can treat val as an rvalue - if so, you get a move. That almost always works, but if it doesn't, fall back to treating it as an lvalue so it compiles with a copy like it used to." This is the one time in the language that lvalues are automatically moved from, and it's because the language/compiler knows that returning from a function will destroy all local variables, so it's safe to move from them.
- banachtarski 10y agoI see that makes sense. I wonder if at some point we should go back to the drawing board on whether the names "lvalue" and "rvalue" make sense. I keep on going back to the mathematical LHS and RHS semantics to figure it out but I can see why other programmers and myself get confused on these sorts of specifics. Giving the compiler the ability to move implicitly surprises me since moves can have side-effects as you've said.
- Manishearth 10y agoI know. I'm not saying it isn't wrong, I'm saying that I can see why he wrote it that way :) A lot of C++ resources explain moving solely in terms of rvalues so it's easy to associate the two in the wrong places.
- santaclaus 10y ago> This is a separate optimization that existed even prior to C++11 called the "named return value optimization" or NRVO for short. And by "sufficient new compiler" you really should just say every compiler. Aren't copy elision guarantees part of the new C++17 standard?
- banachtarski 10y agoYes but they've "practically" been happening already since the C++03 era
- AstralStorm 10y agoOn a bunch of platforms in many cases. If in doubt, check produced assembly.
- krona 10y agoYes, but rust on LLVM will also perform NRVO, will it not? NRVO is a compiler optimization, not a feature of the language itself.
- banachtarski 10y agoI'm not saying Rust doesn't do this. I'm not familiar with Rust but many languages do NRVO as you point out. The important distinction is whether we actually run additional instructions in this variant of the optimization. In C++ at least, no additional instructions are run.
- lorenzhs 10y agoWell, the C++11 standard requires the Compiler to elide the copy (NRVO) if it wants to, or move if it doesn't want to. The language used is very lawyerish, I had to read the section a couple of times to find it even though I knew it was there (it's mentioned in "Effective Modern C++"). So it's up to the compiler to elide the copy (making it a compiler optimisation as you say), but the standard says that it has to move if it doesn't elide. This means that returning a named loca variable by value is always the best choice. Again, see "Effective Modern C++" for more.
- StephanTLavavej 10y agoCopy elision, including the RVO and NRVO, actually has to be blessed by the language (there is a paragraph in the C++ Standard dedicated to it). That's because copy elision skips calls to copy constructors and destructors, which are "observable" (they can have side effects, including printing stuff to the console, etc.). The general "As If Rule" permission to optimize "unobservable" things away doesn't apply here, so special permission to drop the calls is required. This is true going back to C++98 (C++17 strengthened things with guaranteed copy elision).
- banachtarski 10y agoAh and here's the man I originally learned a lot of these concepts from haha. And TIL. I remember programming back in the C++98 days and people were terrified of the copy on return. I had always assumed there was a reason for it but I suppose people might not have understood the behavior super well or I was misinformed at the time.
- nercury 10y agoThanks for your comment, looks like my knowledge is still lacking here. Although I hoped that it would be enough to give an overview, without going into much detail. I have removed a bit where I say that rvalues are same as returned values, to avoid confusing other readers.
- banachtarski 10y agoNo problem :) It's still a good effort nonetheless and I'm sure your readers will appreciate the corrections. I personally also learn the most through writing
- banachtarski 10y agoAnother nitpick: However, if the name was a very big string, i.e. something like file contents, and it would be necessary to ensure no copies for performance reasons, the unique_ptr or shared_ptr would come to the rescue This is also not true at all. You can choose to provide a non-templatized function that only accepts an rvalue reference. For example, http://ideone.com/055ViY http://ideone.com/055ViY does not compile. The purpose of the smart pointers unique_ptr and shared_ptr is to annotate ownership for heap allocated objects (unique vs shared ownership). They do help alleviate the thing you're describing, but shouldn't be used so liberally in that way since they heap allocate!
- AstralStorm 10y agoYou can implement explicit stack pools more easily now in C++17 thanks to PMR. Just remember that platforms have a stack length limits and that such a pool is essentially a global variable you may have to handle.
- tdsamardzhiev 10y agoBjarne Stroustrup rates his own C++ knowledge as 7/10, if that makes you feel better.
- shmerl 10y agoAlso known as copy elision. This subject is surprisingly so obscure, that I literally had to read the C++ standard to have a good grasp of it. Combine it with general move semantics, and things start looking pretty complex. See also: http://en.cppreference.com/w/cpp/language/copy_elision http://en.cppreference.com/w/cpp/language/copy_elision
- nly 10y ago> Returning a stack allocated value does not invoke the move constructor. Unless the 'named return value' you're returning happens to be a named parameter. Example: https://ideone.com/TddXn7 https://ideone.com/TddXn7 The move here actually comes from the return, not passing in the newly constructed object. The compiler is eliding Foo's move constructor in main(), but it can't elide both... even if the function is fully inlined.
- banachtarski 10y agoYes good point. NRVO doesn't apply here and named parameters are in fact moved or copied depending on if they are passed by value or by reference. In your case a move would happen but if you changed the parameter to a reference I think it would copy.
- nly 10y agoYes it would copy, unless you used an explicit std::move(). This is why most of the time you're better off just passing things around by value, and making your classes cheap to move (by, force example, making use of pointers (smart or otherwise)). This results in less code and more manageable semantics.
- detrino 10y agoReturning a stack allocated value is a move in C++11/14. It is also true that in many circumstances a compiler may optimize that move away using RVO/NRVO. Consider this example: http://ideone.com/T872Oj http://ideone.com/T872Oj In F1 the compiler can NRVO away the move. In F2 it cannot. Both will fail to compile if you delete the move constructor.
- poizan42 10y agoDoes that mean that side effects in a move constructor is undefined behavior since you can't know whether it's going to be called? Edit: I see that StephanTLavavej clarified that below.
- fryguy 10y agoEven Borland Turbo C++ from 1992 did this: https://youtu.be/RWavTVo7D3M?t=496 https://youtu.be/RWavTVo7D3M?t=496
- fryguy 10y agoEven Borland Turbo C++ from 1992 did this: https://youtu.be/RWavTVo7D3M?t=496 https://youtu.be/RWavTVo7D3M?t=496
- deleted 10y ago[deleted]
- matthewaveryusa 10y agoTo be fair NRVO is now in the c++ standard. prior to c++11 it was a non-mandatory compiler optimization. Now it's part of the language. It's a good think because optimizations should have no side-effects yet this one does (if your copy ctor has a print statement, it won't be printed if this optimization is deployed.)
- adrianN 10y agoThe compiler I work with doesn't do this optmization in every case, so I can't rely on it. Compilers for embedded platforms are shit.
- mbel 10y agoDid you look at the generated assembly of your demo? RVO probably didn't happen there. "returns_a_foo" was most likely inlined into main and then there was no need of eliding a copy. I'm not saying that you are wrong, only that your demo poorly demonstrates what you are arguing.
- balls187 10y agoThis is why I love HN.