Const References to Temporary Objects

September 2, 2018

Consider the following code snippet:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <iostream>

int GetInt() {
    int x = 1;
    return x;
}

int main() {
    const int& x = GetInt();
    std::cout << x << std::endl;
    return 0;
}

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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <string>

struct Container {
    std::string s;

    Container() {
        s = "Init";
        std::cout << "Constructed" << std::endl;
    }

    ~Container() {
        s = "UNSAFE";
        std::cout << "Destructed" << std::endl;
    }

    const std::string& GetS() const {
        return s;
    }
};

int main() {
    const std::string& s = Container().GetS();
    std::cout << s << std::endl;

    return 0;
}

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:

1
2
3
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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <string>

struct Container {
    std::string s;

    Container() {
        s = "Init";
        std::cout << "Constructed" << std::endl;
    }

    ~Container() {
        s = "UNSAFE";
        std::cout << "Destructed" << std::endl;
    }

    const std::string& GetS() const {
        return s;
    }
};

int main() {
    const std::string& s = Container().s;
    std::cout << s << std::endl;

    return 0;
}

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:

1
2
3
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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <iostream>

int GetInt() {
    int x = 1;
    return x;
}

int main() {
    int& x = GetInt();
    std::cout << x << std::endl;

    return 0;
}

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