C++ is always full of surprises. This week, I learned that C++ has (yet another) special feature that allows the lifetime of a temporary (rvalue) to be lengthen on the stack if it is bound to a reference-to-const.
It means that the following example is legal C++. In this example, the temporary i’s lifetime is extended across its original scope.
int f() { int i = 0; return i; } void main() { int const &cr = f(); // lifespan extended int &cr = f(); // illegal (crash) because f() does not return lvalue }
This is supposed to minimize the number of copies performs.
More Ammo To Blow Your Leg Away
You would think that this feature prevents the temporary from being copied. Apparently not so.
If you run the following program on VC9.0 and VC10.0, obj destructor is called twice, implies that it was copied. [sidenote: gcc 4.3 did not perform the copy]
#include <iostream> class obj { public: ~obj() { std::cout <<"destroyed" << std::endl; } }; void main() { obj const &rco = obj(); }
This is because the standard allows the temporary to be fully copied, and renders the reference-to-const declaration useless.
Here’s the C++ standard’s guideline in section 8.5.3.
If the initializer expression is an rvalue, with T2 a class type, and “cv1 T1” is reference-compatible with “cv2 T2,” the reference is bound in one of the following ways (the choice is implementation defined):
The reference is bound to the object represented by the rvalue or to a subobject with that object
A temporary of type “cv1 T2” is created, and a constructor is called to copy the entire rvalue object into the temporary. This reference is bound to the temporary or to a subobject with the temporary.
No Better Than RVO
Dave Abraham’s copy ellision example demonstrates how smart modern compilers have become. Return Value Optimization works well in both named and unnamed temporaries, and compose nicely.
So why bother adding reference-to-const if it works just as well without it?
Final Thoughts
C++0x (C++0a?) is supposed to address this problem by enforcing the reference-to-const to always bind to the rvalue, instead of its copy. But if a copy can be elided, I expect RVO would have done it as well.
I once asked Bjarne Stroustrup (in a Usenet discussion) about the rationale, and he replied that it provided uniform rules: the same rule for initializing a local reference as for initializing a formal argument reference.
Still, as with any feature, use cases that nobody originally envisioned pop up.
In particular the right destructor is called when you have a
Base const&
and initialize it with aDerived
temporary.And this feature is used in Petru Marginean and Andrei Alexandrescu’s ScopeGuard. They published an article about it in DDJ, and that article + accompanying code code is available on the net. If you’re not familiar with ScopeGuard it’s definitely worth checking out, but note that for MSVC their use of standard
__LINE__
needs to be replaced with MS-specific__COUNTER__
.I vaguely recall seeing a ScopeGuard class added to Boost.
Cheers,
– Alf
I read ScopedGuard article before, and I am not completely sold on it. I don’t enjoy callbacks because I find it disruptive to the flow of the code. ScopedGuard appears to be useful when the “undo” operation is not straight-forward (e.g. conditional). But I tend to write code with simple undo operation where RAII is sufficient.
However, your article on Parts Factory demonstrated a beautiful use of const-reference-to-temp. A default reference-to-const parameter is one I’ve never thought of.
(http://alfps.wordpress.com/2010/05/12/how-to-avoid-post-construction-by-using-parts-factories/#more-120)
Thanks for the lesson.
… Alan
Weird concepts like this only proof how flawed C++ is.
Honestly, I prefer to use C++ as just “C with classes”, without all this fuss.
What if your const reference is not to a temporary?
Are you supposed to know (and assume it will never change) whether it is or isn’t a temporary?
You should write your code without such assumptions.
I think the reference-to-temporary can help show intent, as well. If you want to explicitly limit yourself to just a base class’s methods, you can capture the temporary in a reference-to-base.