Lesson learned: Do Not Store rvalue Reference Outlive its Declaration
I am building a custom operator overloading structures in C++, and was really seduced with what rvalue promises: the lifetime of rvalue reference is bound to the expression of its context.
Rvalue references can be used to extend the lifetimes of temporary objects (cppreference.com)
I was building a combination of operators which accepts a lot of literals and temporary objects including lambda. So for instance:
auto res = ObjectA() * ObjectB() + ObjectC() * ObjectD();
Based on the precedence rules, ObjectA will be ‘multiplied’ with ObjectB, then ObjectC will be ‘multiplied’ with ObjectD, then the result object of those two will be ‘added’. Notes that the order of multiplication is decided by compiler.
So the thing is, inside the overloaded multiplication operator, I want to store the temporary ObjectD, for instance, then carry it over on the addition operator later on when executing the ‘add’ operator. To achieve the “efficiency”, I store the ObjectD temporary inside an rvalue reference so it can be accessed “efficiently” during the execution of the ‘add’ operator.
Then come this rule about binding an rvalue temporary objects to an rvalue reference:
- a temporary bound to a reference parameter in a function call exists until the end of the full expression containing that function call: if the function returns a reference, which outlives the full expression, it becomes a dangling reference. (cppreference.com)
I thought that the full expression will be the entire ObjectA() * ObjectB() + ObjectC() * ObjectD()
. But that is WRONG. Back to the third paragraph of this post, I mentioned the order of evaluation of that expression. And I did not take into account that operator overloading is a function call syntactic sugar! And take a look of the rules quoted above:
until the end of the full expression containing that function call
So of course after returning the overloaded operator function, that reference will may become a dangling reference, then crash or other undefined behavior awaits.
So, yeah, I just fixed that bug in my code, which I thought was a ‘feature’ to perform more efficient storing temporary objects so I don’t have to copy or move all over again. But I was wrong…
So remember the quotes:
Premature optimization is the root of all evil (or at least most of it) in programming. (Donald Knuth)
Let’s sleep.