下面的代码说明了我的担忧:
#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。
据说auto&&
会延长临时对象的生命周期,但我找不到这条规则的标准词,至少在N3690中找不到。
最相关的可能是关于临时对象的第12.2.5节,但不完全是我正在寻找的内容。
那么,auto&amp;&amp;生命周期扩展规则适用于所有表达式中涉及的临时对象,还是仅适用于最终结果?
更具体一点,a.val
在我们到达案例1的范围结束之前保证有效(非悬空)吗?
修改 我更新了示例以显示更多案例(3&amp; Ex)。
你会看到,只有在案例1中,O的生命周期才会延长。
答案 0 :(得分:5)
与const
的引用相同:
const auto& a = wrapper{O()};
或
const wrapper& a = wrapper{O()};
或
wrapper&& a = wrapper{O()};
更具体一点,
a.val
在我们到达案例1的范围结束之前保证有效(非悬空)吗?
是的,是的。
这里({几乎)没有什么特别重要的auto
。它只是一个占位符,用于正确的类型(wrapper
),由编译器推导出来。重点是临时绑定到引用的事实。
有关详细信息,请参阅我引用的A Candidate For the “Most Important const”:
通常,临时对象只会持续到它出现的完整表达式的结尾。但是,C ++故意指定将临时对象绑定到堆栈上对const的引用会延长临时对象到引用本身生命周期的生命周期
这篇文章是关于C ++ 03但是参数仍然有效:临时可以绑定到const
的引用(但不能引用非const
)。在C ++ 11中,临时的也可以绑定到右值引用。在这两种情况下,临时的生命周期都会延长到参考的生命周期。
C ++ 11标准的相关部分正是OP中提到的那些部分,即12.2 p4和p5:
4 - 有两种情况下,临时状态被摧毁 不同于完整表达的结束点。第一个背景 是[...]
5 - 第二个上下文是指引用绑定到临时的。 [...]
(这些行后面的项目符号中有一些例外情况。)
更新 :(关注texasbruce的评论。)
案例2中O
的生命周期较短的原因是我们auto a = wrapper{O()};
(请参阅此处没有&
)然后临时不< / strong>绑定到引用。实际上,临时使用编译器生成的复制构造函数复制到a
。因此,临时文件的生命周期没有扩展,并在它出现的完整表达式结束时死亡。
此特定示例存在危险,因为wrapper::val
是参考。编译器生成的wrapper
的复制构造函数将a.val
绑定到临时val
成员绑定的同一对象。此对象也是临时类型O
。然后,当后者临时死亡时,我们会在屏幕上看到~O()
并a.val
悬挂!
对比案例2:
std::cout << "case 3-----------\n";
{
O o;
auto a = wrapper{o};
std::cout << "end-scope\n";
}
输出是(使用选项-fno-elide-constructors
gcc编译时)
case 3-----------
~wrapper()
end-scope
~wrapper()
~O()
现在,临时wrapper
已将val
成员绑定到o
。请注意o
不是临时的。正如我所说,a
是wrapper
临时的副本,而a.val
也是o
的副本。
wrapper
。在范围结束之前,临时~wrapper()
就会死亡,我们会在屏幕上看到第一个end-scope
。
然后范围结束,我们得到a
。现在,必须按照构造的相反顺序销毁o
和~wrapper()
,因此我们会在a
死亡时看到~O()
,最后o
看到a.val
时间。这表明-fno-elide-constructors
没有悬挂。
(最后评论:我使用{{1}}来阻止与复制构造相关的优化,这会使讨论复杂化,但这是另一个story。)