5 ms·
Two important points: - For std::array of uint8_t – an in-place container of a plain-old-data type – the move constructor and the copy constructor, if not opti
by comex 6y ago
Two important points:
- For std::array of uint8_t – an in-place container of a plain-old-data type – the move constructor and the copy constructor, if not optimized away, will do the exact same thing! Both boil down to copying the bytes from the old object to the new object. So there is never any benefit to calling std::move on such an object. This is different from out-of-line containers like std::vector, where the std::vector object itself consists only of a pointer and some lengths. In that case, the move constructor just copies the pointer and length values themselves and nulls them out in the original object, without copying the underlying data. But the copy constructor has to allocate a whole new heap buffer, copy the underlying data, and initialize the new vector object with that pointer.
- The godbolt links are building without optimization enabled (without -O settings)! GCC apparently still performs (non-mandatory) NRVO in this case, probably because that optimization is implemented on the frontend rather than the backend, and the frontend tends to care less about -O settings. But GCC does not perform the usual suite of backend optimizations, including inlining and memcpy elimination. If optimizations were enabled, GCC might be able to optimize both versions into the same thing, depending on what actually happens in “// Do some computation”. It doesn’t make sense to make performance comparisons with optimization disabled, unless you’re trying to improve the performance of debug builds themselves.
- jfkrorkt 6y agoAm I right to say that using std::move is even a logical mistake here? The purpose of move is to "steal" the underlying memory, but if you do that, you leave the gfx variable from the class without backing. So basically you would need to reallocate it after every call to get_display_pixels. So the code only works because the author uses array, which is statically allocated and there is nothing to steal. It would break were they to switch to vector. Either the author doesn't understand what the purpose of move is, or if this is really their intent, it's a terrible design, since get_display_pixels looks like an idempotent getter function, but it's not.
- foota 6y agoMove leaves the object in a valid but unspecified state. So you can continue to use the existing object.
- w0utert 6y agoYes that was what I was thinking as well, std::move on the return value of a function that returns some private member by value makes no sense (someone please correct me if I’m mistaken here). In the example in the article, the compiler may be able to perform all kinds of optimizations for example because the result is assigned to a const lvalue, and depending on the rest of the code in that function (which is not shown) it may be able to infer the assigned variable can never be aliased, so that’s one possible reason why the disassembly may be surprising. The C++ specification only prescribes how code should behave but it explicitly does not put restrictions how the compiler achieves that effect, to the point whole functions might be completely absent in the produced binary and replaced by a constant if it's possible to calculate the result of every invocation at compile time.
- tylerhou 6y ago> So the code only works because the author uses array, which is statically allocated and there is nothing to steal. It would break were they to switch to vector. No, because get_display_pixels returns by value and not by reference. So the move constructor will move from a newly constructed value, not the instance gfx variable. If get_display_pixels returned by mutable reference and the author used std::vector, yes, the code might break. Nit: array is not statically allocated; it's stack/automatically allocated.
- w0utert 6y agoIt would not break, but it’s still a logical fallacy to std::move something returned by value, no? Or is there a valid use case here? I strictly use std::move in case of explicit transfer of ownership, and not to try to outsmart the compiler to ‘optimize’ something. As the article shows the compiler is smart enough to do all this by itself, it doesn’t need to be told to try to elide copies. But maybe I’m missing some valid use case where it would make sense to add the std::move in these kinds of situations?
- tylerhou 6y agoYou're right, there's not really a point in std::move-ing something returned by value because the something is already a rvalue.
- jfkrorkt 6y agoYou are right, the move is after the function returns (a copy). Thanks for pointing it out. Btw, that's what I meant by statically allocated, I wasn't sure what's the proper terminology.
- qppo 6y agoMove constructors can write into overlapping memory while copy constructors cannot, iirc. I thought that compilers would use this distinction to allow for memmove instead of memcpy in defaulted move/copy constructors even for POD but I could be wrong.
- comex 6y agoYou mean memcpy instead of memmove? I'm pretty sure that all types of constructors expect `this` to be non-aliased, so the compiler could use memcpy for both move and copy constructors. I also checked move and copy assignment operators – is it UB to move-assign an object to itself? Based on this very confusing Stack Overflow thread [1], I think the answer is: no, not at a language level, although it might be bad to do it with standard library types. If that's correct, a compiler-generated default move assignment operator would have to assume aliasing is possible and use memmove, same as with copy assignments. [1] https://stackoverflow.com/questions/13127455/what-does-the-standard-library-guarantee-about-self-move-assignment https://stackoverflow.com/questions/13127455/what-does-the-s...
- qppo 6y agoNo I meant that the only difference between a move and a copy under the hood is that the compiler can assume the destination memory overlaps the source. If the struct is large that might mean using a memmove instead of memcpy. I don't think aliasing the this pointer matters, since it's the pointed-to memory that could be aliased. But I'm not sure if that's actually faster or if any compiler utilizes the semantic difference.
- jdashg 6y agoFor perf, one generally uses memcpy unless memmove's semantics are needed. I think you have confused memcpy vs -move with copy vs move ctors. Certainly your last sentence is not correct for any reasonably written code. Check the docs yourself, but this doesn't match my understanding.