Const References to Temporary Objects
Consider the following code snippet:
1#include <iostream>
2
3int GetInt() {
4 int x = 1;
5 return x;
6}
7
8int main() {
9 const int& x = GetInt();
10 std::cout << x << std::endl;
11 return 0;
12}
In particular, pay attention to line #9. Is it guaranteed to be safe?
In this case, the answer is yes. The C++ standard guarantees that binding a temporary to a const reference on the stack, extends the lifetime of the temporary to the lifetime of the const reference. This has been covered in GotW #88.
But is it always safe to declare local variables as const references, whenever you can?
Well, this is C++, so the answer is: It depends :)
Consider this code snippet:
1#include <iostream>
2#include <string>
3
4struct Container {
5 std::string s;
6
7 Container() {
8 s = "Init";
9 std::cout << "Constructed" << std::endl;
10 }
11
12 ~Container() {
13 s = "UNSAFE";
14 std::cout << "Destructed" << std::endl;
15 }
16
17 const std::string& GetS() const {
18 return s;
19 }
20};
21
22int main() {
23 const std::string& s = Container().GetS();
24 std::cout << s << std::endl;
25
26 return 0;
27}
What is the output? Is line 23 always safe? It creates a temporary |Container()| and then calls a member function on that temporary, which in turn returns a const reference to a member variable.
The answer is NO! In fact the output is:
Constructed
Destructed
UNSAFE
Turns out that the temporary lifetime extension rule applies only to direct references to temporary objects, not to references obtained indirectly via a member function. e.g. consider this:
1#include <iostream>
2#include <string>
3
4struct Container {
5 std::string s;
6
7 Container() {
8 s = "Init";
9 std::cout << "Constructed" << std::endl;
10 }
11
12 ~Container() {
13 s = "UNSAFE";
14 std::cout << "Destructed" << std::endl;
15 }
16
17 const std::string& GetS() const {
18 return s;
19 }
20};
21
22int main() {
23 const std::string& s = Container().s;
24 std::cout << s << std::endl;
25
26 return 0;
27}
It does a similar thing except line 23 takes a direct reference to the member variable. Is this safe?
In this case, the answer is YES!. The output is:
Constructed
Init
Destructed
i.e. the destruction of the temporary |Container()| is held off until |const std::string& s| can be safely destructed.
Note however, that this is limited to const references and not ordinary references. This will not compile:
1#include <iostream>
2
3int GetInt() {
4 int x = 1;
5 return x;
6}
7
8int main() {
9 int& x = GetInt();
10 std::cout << x << std::endl;
11
12 return 0;
13}
Check line 9. It is an ordinary reference and not a const reference.
Further Reading
Check out TotW #101 and TotW #107. “Tips of the Week (TotW)” is a Google-internal publication, which is gradually being released into the public. Do check them out.