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

☆樱花仙子☆ 提交于 2019-12-01 03:23:54
Cassio Neri

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.)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!