Const References to Temporary Objects

Posted on Sep 2, 2018

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.

comments powered by Disqus