How would auto&& extend the life-time of the temporary object?

后端 未结 1 1419
故里飘歌
故里飘歌 2021-01-11 18:49

The code below illustrated my concern:

#include 


struct O
{
    ~O()
    {
        std::cout << \"~O()\\n\";
    }
};

struct wrapper         


        
1条回答
  •  一整个雨季
    2021-01-11 19:32

    In the same way that a reference to const does:

    const auto& a = wrapper{O()};
    

    or

    const wrapper& a = wrapper{O()};
    

    or also

    wrapper&& a = wrapper{O()};
    

    More specific, is a.val guaranteed to be valid (non-dangling) before we reach the end-of-scope in case 1?

    Yes, it is.

    There's (almost) nothing particularly important about auto here. It's just a place holder for the correct type (wrapper) which is deduced by the compiler. The main point is the fact that the temporary is bound to a reference.

    For more details see A Candidate For the “Most Important const” which I quote:

    Normally, a temporary object lasts only until the end of the full expression in which it appears. However, C++ deliberately specifies that binding a temporary object to a reference to const on the stack lengthens the lifetime of the temporary to the lifetime of the reference itself

    The article is about C++ 03 but the argument is still valid: a temporary can be bound to a reference to const (but not to a reference to non-const). In C++ 11, a temporary can also be bound to an rvalue reference. In both cases, the lifetime of the temporary is extended to the lifetime of the reference.

    The relevant parts of the C++11 Standard are exactly those referred in the OP, that is, 12.2 p4 and p5:

    4 - There are two contexts in which temporaries are destroyed at a different point than the end of the full expression. The first context is [...]

    5 - The second context is when a reference is bound to a temporary. [...]

    (There are some exceptions in the bullet points following these lines.)

    Update: (Following texasbruce's comment.)

    The reason why the O in case 2 has a short lifespan is that we have auto a = wrapper{O()}; (see, there's no & here) and then the temporary is not bound to a reference. The temporary is, actually, copied into a using the compiler generated copy-constructor. Therefore, the temporary doesn't have its lifetime expanded and dies at the end of the full expression in which it appears.

    There's a danger in this particular example because wrapper::val is a reference. The compiler generated copy-constructor of wrapper will bind a.val to the same object that the temporary's val member is bound to. This object is also a temporary but of type O. Then, when this latter temporary dies we see ~O() on the screen and a.val dangles!

    Contrast case 2 with this:

    std::cout << "case 3-----------\n";
    {
        O o;
        auto a = wrapper{o};
        std::cout << "end-scope\n";
    }
    

    The output is (when compiled with gcc using option -fno-elide-constructors)

    case 3-----------
    ~wrapper()
    end-scope
    ~wrapper()
    ~O()
    

    Now the temporary wrapper has its val member bound to o. Notice that o is not a temporary. As I said, a is a copy of the wrapper temporary and a.val also binds to o. Before the scope ends the temporary wrapper dies and we see the first ~wrapper() on the screen.

    Then the scope ends and we get end-scope. Now, a and o must be destroyed in the reverse order of construction, hence we see ~wrapper() when a dies and finally ~O() when it's o's time. This shows that a.val doesn't dangle.

    (Final remark: I've used -fno-elide-constructors to prevent a optimization related to copy-construction that would complicate the discussion here but this is another story.)

    0 讨论(0)
提交回复
热议问题