auto&& amp;延长临时物体的使用寿命?

时间:2013-11-08 16:39:19

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

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

#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的生命周期才会延长。

1 个答案:

答案 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不是临时的。正如我所说,awrapper临时的副本,而a.val也是o的副本。 wrapper。在范围结束之前,临时~wrapper()就会死亡,我们会在屏幕上看到第一个end-scope

然后范围结束,我们得到a。现在,必须按照构造的相反顺序销毁o~wrapper(),因此我们会在a死亡时看到~O(),最后o看到a.val时间。这表明-fno-elide-constructors没有悬挂。

(最后评论:我使用{{1}}来阻止与复制构造相关的优化,这会使讨论复杂化,但这是另一个story。)