#include <iostream> struct O { ~O() { std::cout << "~O()\n"; } }; struct wrapper { O const& val; ~wrapper() { std::cout << "~wrapper()\n"; } }; struct wrapperEx // with explicit ctor { O const& val; explicit wrapperEx(O const& val) : val(val) {} ~wrapperEx() { std::cout << "~wrapperEx()\n"; } }; template<class T> T&& f(T&& t) { return std::forward<T>(t); } int main() { std::cout << "case 1-----------\n"; { auto&& a = wrapper{O()}; std::cout << "end-scope\n"; } std::cout << "case 2-----------\n"; { auto a = wrapper{O()}; std::cout << "end-scope\n"; } std::cout << "case 3-----------\n"; { auto&& a = wrapper{f(O())}; std::cout << "end-scope\n"; } std::cout << "case Ex-----------\n"; { auto&& a = wrapperEx{O()}; std::cout << "end-scope\n"; } return 0; }
看到现场here.
据说汽车&&将延长临时对象的终身时间,但是我在这个规则上找不到标准单词,至少不在N3690中.
最相关的可能是关于临时对象的第12.2.5节,但不完全是我正在寻找的.
那么,自动和&终生时间扩展规则适用于表达式中涉及的所有临时对象,还是仅适用于最终结果?
更具体地说,在我们到达范围1之前,a.val保证是有效的(非悬挂)?
编辑:
我更新了示例以显示更多的案例(3& Ex).
你会看到,只有在情况1,O的生命周期被延长.
解决方法
const auto& a = wrapper{O()};
要么
const wrapper& a = wrapper{O()};
或者也
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?是的.
在这里,几乎没有什么特别重要的汽车.它只是编译器推导的正确类型(包装)的占位符.主要的一点是临时被绑定到一个参考.
有关详细信息,请参阅A Candidate For the “Most Important const”我引用:
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
这篇文章是关于C 03,但参数仍然有效:一个临时可以绑定到一个引用const(但不是引用非const).在C 11中,临时值也可以绑定到r值引用.在这两种情况下,临时的生命周期都延长到引用的生命周期.
C11标准的相关部分正是OP中提到的那些,即12.2 p4和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. […]
(在这些行后面的项目符号中有一些例外.)
更新:(按照texasbruce的评论.)
情况2的寿命短的原因是我们有自动a =包装{O()}; (见,没有&这里),然后暂时没有绑定到引用.实际上,临时是使用编译器生成的拷贝构造函数进行复制的.因此,暂时的终身没有扩张,在完整的表达结束时就会死亡.
在这个特定的例子中有一个危险,因为wrapper :: val是一个引用.编译器生成的wrapper构造函数将绑定a.val到与临时val成员绑定的同一个对象.这个对象也是一个临时的,但是类型为O.然后,当这个对象暂时死亡时,我们在屏幕上看到〜O(),并且a.val dangles!
对比案例2与此:
std::cout << "case 3-----------\n"; { O o; auto a = wrapper{o}; std::cout << "end-scope\n"; }输出是(当使用gcc编译时使用选项-fno-elide-constructors)
case 3----------- ~wrapper() end-scope ~wrapper() ~O()现在临时包装器的val成员绑定到o.注意o不是临时的.正如我所说,a是临时包装的副本,a.val也绑定到
O操作.在范围结束之前,临时包装器死亡,我们在屏幕上看到第一个〜wrapper().然后范围结束,我们得到终点.现在,a和o必须以相反的构造顺序被销毁,所以我们看到〜wrapper()当一个死亡,最后〜O()当它的时间.这表明a.val不会发生变化.
(最后的话:我已经使用-fno-elide-constructors来防止与复制结构相关的优化,这将使这里的讨论复杂化,但这是另一个story.)