Steve Lorimer
Steve Lorimer

Reputation: 28659

Does this const reference have its life preserved?

I have found this answer to the question "Does a const reference prolong the life of a temporary?", which states:

Only local const references prolong the lifespan.

I'm afraid my standardese is not up to scratch to know whether foo, below, is a local const reference or not.

Does my const std::string& foo below prolong the lifetime of the temporary std::string function argument created in the call to get_or, or do I have a dangling reference?

#include <iostream>
#include <boost/optional.hpp>

struct Foo
{
    const std::string& get_or(const std::string& def)
    {
        return str ? str.get() : def;
    }

    boost::optional<std::string> str;
};

int main()
{
    Foo f;
    const std::string& foo = f.get_or("hello world");

    std::cout << foo << '\n';
}

Upvotes: 8

Views: 269

Answers (3)

Xirema
Xirema

Reputation: 20396

const& won't extend lifetimes in that situation. Consider the example here that constructs a temporary and then attempts to print it: it's using the same constructs as your code, but I've altered it to make object construction and destruction more explicit to the user.

#include <iostream>

struct reporting {
    reporting() { std::cout << "Constructed" << std::endl;}
    ~reporting() { std::cout << "Destructed" << std::endl;}
    reporting(reporting const&) { std::cout << "Copy-Constructed" << std::endl;}
    reporting(reporting &&) { std::cout << "Move-Constructed" << std::endl;}
    reporting & operator=(reporting const&) { std::cout << "Copy-Assigned" << std::endl; return *this;}
    reporting & operator=(reporting &&) { std::cout << "Move-Assigned" << std::endl; return *this;}

    void print() const {std::cout << "Printing." << std::endl;}
};

const reporting& get_or(const reporting& def)
{
    return def;
}

int main()
{
    const reporting& foo = get_or(reporting{});

    foo.print();
    return 0;
}

Output:

Constructed
Destructed
printing.

Note how the object is destroyed before printing. is displayed.

You might be wondering why the code still completes with no visible errors: it's the result of Undefined Behavior. The object in question doesn't exist, but because it doesn't depend on state to invoke its method, the program happens to not crash. Other, more complicated examples should carry no guarantee that this will work without crashing or causing other, unexpected behavior.

Incidentally, things are a little different if the temporary is bound directly to the const&:

#include <iostream>

struct reporting {
    reporting() { std::cout << "Constructed" << std::endl;}
    ~reporting() { std::cout << "Destructed" << std::endl;}
    reporting(reporting const&) { std::cout << "Copy-Constructed" << std::endl;}
    reporting(reporting &&) { std::cout << "Move-Constructed" << std::endl;}
    reporting & operator=(reporting const&) { std::cout << "Copy-Assigned" << std::endl; return *this;}
    reporting & operator=(reporting &&) { std::cout << "Move-Assigned" << std::endl; return *this;}

    void print() const {std::cout << "printing." << std::endl;}
};

const reporting& get_or(const reporting& def)
{
    return def;
}

int main()
{
    const reporting& foo = reporting{};

    foo.print();
    return 0;
}

Output:

Constructed
printing.
Destructed

See how the object isn't destroyed until after it is used. In this situation, the object survives until the end of scope.

Upvotes: 4

Stephan Lechner
Stephan Lechner

Reputation: 35154

The "temporary" in question is the std::string-object created when calling get_or with a parameter of type const char*. The lifetime of this temporary object is limited with the end of function get_or, and the fact that you return a reference to this temporary and assign it afterwards does not prolong the lifetime. See the following code which uses a simple "custom" string class, which couts construction and destruction:

class MyString {
public:
    MyString (const char* str) {
        m_str = strdup(str);
        cout << "constructor MyString - '" << m_str << "'" << endl;
    }
    ~MyString() {
        cout << "destructor MyString - '" << m_str << "'" << endl;
        free(m_str);
    }
    char *m_str;
};

struct Foo
{
    const MyString& get_or(const MyString& def)
    {
        cout << "Foo::get_or with '" << def.m_str << "'" << endl;
        return def;
    }
};

int main()
{
    Foo f;
    const MyString& foo = f.get_or("hello world");
    cout << "usage of foo?" << endl;
}

Output:

constructor MyString - 'hello world'
Foo::get_or with 'hello world'
destructor MyString - 'hello world'
usage of foo?

Note that the destructor is called before you will have the chance to use foo.

The situation is different if you assign a reference to a temporary directly. Again, the lifetime is until the end of the function main, but it will be used in main and not in any function calling main:

const MyString& foo2 = MyString("hello world2");
cout << "usage of foo..." << endl;

Then the output will be:

constructor MyString - 'hello world2'
usage of foo...
destructor MyString - 'hello world2'

Upvotes: 1

user2357112
user2357112

Reputation: 280778

You passed the string through too many references.

Binding the temporary string to the def parameter of get_or extends the lifetime of the string to the end of the full expression containing the function call, but binding def to the return value of get_or and binding the return value of get_or to foo do not extend the lifetime further. The string is dead by the time you try to print it.

Upvotes: 2

Related Questions