Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does this const reference have its life preserved?

Tags:

c++

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';
}
like image 339
Steve Lorimer Avatar asked May 31 '17 20:05

Steve Lorimer


People also ask

Can you change a const reference?

But const (int&) is a reference int& that is const , meaning that the reference itself cannot be modified.

What does const reference mean?

A const reference is actually a reference to const. A reference is inherently const, so when we say const reference, it is not a reference that can not be changed, rather it's a reference to const. Once a reference is bound to refer to an object, it can not be bound to refer to another object.

What is lifetime in c++?

C/C++ use lexical scoping. The lifetime of a variable or object is the time period in which the variable/object has valid memory. Lifetime is also called "allocation method" or "storage duration."

How can you extend the life time of an object?

The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details.


3 Answers

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.

like image 122
Xirema Avatar answered Nov 16 '22 04:11

Xirema


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.

like image 20
user2357112 supports Monica Avatar answered Nov 16 '22 04:11

user2357112 supports Monica


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'
like image 28
Stephan Lechner Avatar answered Nov 16 '22 04:11

Stephan Lechner