10

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

#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;
}

看到它住在这里

据说auto&&会延长临时对象的寿命,但我找不到这个规则的标准词,至少在N3690中没有。

最相关的可能是关于临时对象的第 12.2.5 节,但不完全是我正在寻找的。

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

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

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

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

4

1 回答 1

6

与引用相同const

const auto& a = wrapper{O()};

或者

const wrapper& a = wrapper{O()};

或者也

wrapper&& a = wrapper{O()};

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

是的。

这里(几乎)没有什么特别重要的auto。它只是编译器推断的正确类型 ( ) 的占位符。wrapper要点是临时绑定到引用的事实。

有关更多详细信息,请参阅我引用的“最重要的 const”</a> 的候选者:

通常,临时对象只持续到它出现的完整表达式的结尾。但是,C++ 故意指定将临时对象绑定到堆栈上对 const 的引用会将临时对象的生命周期延长到引用本身的生命周期

这篇文章是关于 C++ 03 但论点仍然有效:临时可以绑定到对的引用const(但不能绑定到对非的引用const)。在 C++ 11 中,临时可以绑定到右值引用。在这两种情况下,临时的生命周期都会延长到引用的生命周期。

C++11 标准的相关部分正是 OP 中提到的那些,即 12.2 p4 和 p5:

4 - 有两种情况,其中临时对象在与完整表达式结尾不同的点被销毁。第一个上下文是[...]

5 - 第二个上下文是引用绑定到临时的。[...]

(这些行后面的项目符号中有一些例外。)

更新:(遵循 texasbruce 的评论。)

案例 2 生命周期短的原因O是我们有auto a = wrapper{O()};(看,这里没有&),然后临时没有绑定到引用。实际上,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 编译时使用 option -fno-elide-constructors

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阻止与复制构造相关的优化,这会使这里的讨论复杂化,但这是另一个故事。)

于 2013-11-08T18:06:05.423 回答