C++17 promised to introduce Copy Elision as a requirement, so I've upgraded from C++14 all the way to C++20. Just for that. (RVO as an optional behavior-altering optimization... makes me genuinely motion-sick when running a program in my head.) I'm very new to this version of C++, but I want to prescribe the behavior of returning an object completely; whether it calls it's copy method and a temp's destructor, or does not.
Object f() {
Object x;
x.value = 10;
x.str = "example function";
return x;
}
Is there anything special I must do (or change) in this f()
function to ensure the returned object always elides calling it's copy or move constructor and x
's destructor? Also, is there any way to ask the compiler to abort compilation if it can't do so? I don't want to accidentally give the compiler the ability to choose, if I can help it.
Check out cppreference's page on copy elision. Since C++17, there is a guaranteed form and a non-mandatory form. Technically, guaranteed copy elision is not even considered copy elision anymore, it is simply a change in the specification of prvalues.
"Guaranteed copy elision" happens when initializing an object (including in a return statement) with a prvalue of the same class type (ignoring cv-qualification). By definition, a prvalue has no name, and so, before C++17 and in the case of a return statement, this was an non-mandatory optimization called unnamed return value optimization (URVO). Since C++17, it is replaced by a fundamental change in the language:
a prvalue is not materialized until needed, and then it is constructed directly into the storage of its final destination.
In your code, x
is named, so it is not a prvalue, and as such its copy (or move) is not guaranteed to be elided. Nevertheless, I believe most modern compilers will perform copy elision in this case, unless you specifically ask them not to (for example with -fno-elide-constructors
for GCC and Clang). It is called named return value optimization (NRVO), and it is one of only two optimizations that may change the observable side-effects. Note that NRVO is forbidden in the context of a constant expression.
If you want a guarantee that no copy or move is performed, then you need to handle a prvalue instead:
Object f() {
int value = 10;
std::string str = "example function";
return Object(value, str); // this is a prvalue being returned
}
// simpler
Object f() {
return Object(10, "example function");
}
// even simpler if Object's constructor is implicit
Object f() {
return {10, "example function"};
}