I have a very basic question in C++. How to avoid copy when returning an object ?
Here is an example :
std::vector test(const uns
The move constructor is guaranteed to be used if NRVO does not happen
Therefore, if you return an object with move constructor (such as std::vector
) by value, it is guaranteed not to do a full vector copy, even if the compiler fails to do the optional NRVO optimization.
This is mentioned by two users who appear influential in the C++ specification itself:
Not satisfied by my Appeal to Celebrity?
OK. I can't fully understand the C++ standard, but I can understand the examples it has! ;-)
Quoting the C++17 n4659 standard draft 15.8.3 [class.copy.elision] "Copy/move elision"
3 In the following copy-initialization contexts, a move operation might be used instead of a copy operation:
- (3.1) — If the expression in a return statement (9.6.3) is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or
- (3.2) — if the operand of a throw-expression (8.17) is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one),
overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If the first overload resolution fails or was not performed, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. — end note ]
4 [ Example:
class Thing { public: Thing(); ~ Thing(); Thing(Thing&&); private: Thing(const Thing&); }; Thing f(bool b) { Thing t; if (b) throw t; // OK: Thing(Thing&&) used (or elided) to throw t return t; // OK: Thing(Thing&&) used (or elided) to return t } Thing t2 = f(false); // OK: no extra copy/move performed, t2 constructed by call to f struct Weird { Weird(); Weird(Weird&); }; Weird g() { Weird w; return w; // OK: first overload resolution fails, second overload resolution selects Weird(Weird&) }
— end example
I don't like the "might be used" wording, but I think the intent is to mean that if either "3.1" or "3.2" hold, then the rvalue return must happen.
This is pretty clear on the code comments for me.
Pass by reference + std::vector.resize(0)
for multiple calls
If you are making multiple calls to test
, I believe that this would be slightly more efficient as it saves a few malloc()
calls + relocation copies when the vector doubles in size:
void test(const unsigned int n, std::vector& x) {
x.resize(0);
x.reserve(n);
for (unsigned int i = 0; i < n; ++i) {
x.push_back(i);
}
}
std::vector x;
test(10, x);
test(20, x);
test(10, x);
given that https://en.cppreference.com/w/cpp/container/vector/resize says:
Vector capacity is never reduced when resizing to smaller size because that would invalidate all iterators, rather than only the ones that would be invalidated by the equivalent sequence of pop_back() calls.
and I don't think compilers are able to optimize the return by value version to prevent the extra mallocs.
On the other hand, this:
so there is a trade-off.