RVO (Return Value Optimization) and deleted move constructors pitfall.
Xiahua Liu January 14, 2025 #LinuxThe Surprising Case
// filepath: rvo_example.cpp
;
Widget
Why This Happens
Based on the C++ standards here:
Under the following circumstances, the compilers are permitted, but not required to omit the copy and move(since C++11) construction of class objects even if the copy/move(since C++11) constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. This is an optimization: even when it takes place and the copy/move(since C++11) constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed:
And because of this:
In a return statement or a throw expression, if the compiler cannot perform copy elision but the conditions for copy elision are met, or would be met except that the source is a function parameter, the compiler will attempt to use the move constructor even if the source operand is designated by an lvalue(until C++23) the source operand will be treated as an rvalue(since C++23); see return statement for details.
In human words
- RVO applied or not, compiler needs valid move/copy constructors.
- Deleted move constructor is present but invalid.
- Compiler tries move before copy.
Solution
The solution is simple, we can remove the = delete
declaration, and because Widget
has a copy constructor, the move constructor will not be implicitedly declared. So the compiler will not use the move constructor of Widget
in this case, because it is not present, and will use the copy constructor instead.
// filepath: solution.cpp
;
// Now works fine - uses copy constructor when NRVO not applied
Widget
Other notes : Avoid side effects in copy/move constructors
Copy elison allows the compiler to omit the copy/move constructor while returning an object, this means if the copy/move constructor has side effects, the compiler is allowed to ignore those side effects when copy elison happens.
This means in general your C++ code should not have copy/move constructor with side effects, such as the shared_ptr
copy constructor. Otherwise the behavior will be undefined.