【问题标题】:How would auto&& extend the life-time of the temporary object?auto&& 如何延长临时对象的生命周期?
【发布时间】:2013-11-20 18:21:45
【问题描述】:

下面的代码说明了我的担忧:

#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&amp;&amp;会延长临时对象的生命周期,但是我找不到这条规则的标准词,至少在N3690中找不到。

最相关的可能是关于临时对象的第 12.2.5 节,但不完全是我想要的。

那么,auto&& 生命周期延长规则是否适用于所有表达式中涉及的临时对象,还是仅适用于最终结果?

更具体地说,a.val 在我们到达案例 1 的范围结束之前是否保证有效(非悬空)?

编辑: 我更新了示例以显示更多案例(3 和 Ex)。

您会看到,只有在情况 1 中,O 的生命周期才会延长。

【问题讨论】:

  • auto&amp;&amp; val = wrapper{O()}.val,没有。
  • @Jamboree 您是专门询问auto&amp;&amp; 还是只是延长寿命? (推导出auto&amp;&amp; 后,引用绑定的默认规则适用。要么是wrapper const&amp;,要么是wrapper&amp;&amp;,否则绑定将失败。)
  • @Jamboree IMO N3690 目前是一个奇怪的文档。它包含一些 C++1y 特性,但 N3979 是最新的公开草案(除了 github repository)。
  • open-std.org/JTC1/SC22/WG21/docs/cwg_active.html#1697。 EDG 和 GCC 不会延长使用寿命。我对意图的理解到目前为止不会被扩展(我认为实现的行为“正确”而不是标准中的描述)。我们需要看看这个问题将如何解决,以及官方的实现是否会出错。
  • 特别是看 12.2p5 中的最后一个项目符号示例,它还初始化了一个引用成员,根据示例的注释,您可以在其中看到生命周期延长

标签: c++ c++11 auto temporary lifetime


【解决方案1】:

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()};(请参阅,这里没有 &amp;),然后临时值 绑定到参考。实际上,使用编译器生成的复制构造函数将临时复制到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";
}

输出是(使用 gcc 编译时使用选项 -fno-elide-constructors

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

现在临时wrapperval 成员绑定到o。请注意,o 不是临时的。正如我所说,awrapper 临时的副本,a.val 也绑定到 o。在作用域结束之前,临时的wrapper 消失了,我们在屏幕上看到了第一个~wrapper()

然后作用域结束,我们得到end-scope。现在,ao 必须按照构造的相反顺序销毁,因此当a 死亡时我们会看到~wrapper(),而在o 的时间最后是~O()。这表明a.val 没有悬空。

(最后一句话:我使用-fno-elide-constructors 来防止与复制构造相关的优化,这会使这里的讨论复杂化,但这是另一个story。)

【讨论】:

  • 如果你看现场演示,有一些奇怪的事情......第二个wrapper中的O寿命很短
  • @texasbruce 我已经更新了帖子以提供进一步的解释。我希望这有助于澄清问题。
  • 刚刚用谷歌搜索了对右值的悬空引用...有趣的是该类被复制但引用成员被破坏并悬空......
  • 备注:所以引用不是传递的,请参阅我的附加案例(3&Ex),其中生命周期没有延长。使用默认初始化程序的情况 1 确实是一个特例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-01-06
  • 1970-01-01
  • 2012-11-15
  • 1970-01-01
  • 2023-02-03
  • 1970-01-01
相关资源
最近更新 更多