【问题标题】:Extended lifetime of an object returned from function从函数返回的对象的延长生命周期
【发布时间】:2017-09-07 01:40:29
【问题描述】:

对于从函数返回并绑定到右值/常量左值引用的对象的生命周期延长,我有一些不清楚的信息。来自here的信息。

在 return 语句中临时绑定到函数的返回值不会被扩展:它在 return 表达式的末尾立即被销毁。这样的函数总是返回一个悬空引用。

如果我理解正确,引用声称 return 语句返回的对象的生命周期是不可扩展的。但最后一句话表明,这只适用于返回引用的函数。

在 GCC 上,此代码产生以下输出:

struct Test
{
    Test() { std::cout << "creation\n"; }
    ~Test() { std::cout << "destruction\n"; }
};
    
Test f()
{
    return Test{};   
}
    
int main()
{
    std::cout << "before f call\n";
    Test && t = f();
    std::cout << "after f call\n";
}

before f call
creation
after f call
destruction

所以看起来寿命延长了。
绑定到此类引用的临时对象的生命周期是否应该延长?另外能否提供更明确的信息来源?

【问题讨论】:

  • 是的,应该,但我不知道它是否在标准中指定。
  • Test &amp;&amp; t = f(); - 该代码没有意义。为什么将对象声明为右值?
  • @Criss 抱歉,删除了评论,因为我忽略了 &amp;&amp; 这可能会导致我不熟悉的东西。但是,我不太明白这个问题,因为您的 f 没有返回参考,而是本地临时的副本,所以一切都应该没问题
  • @jaskmar 这不是一个右值,它是一个右值引用——这是两个不同的东西。右值是一个值类别,右值引用是一个类型
  • hm...也许返回的临时文件的生命周期应该在行尾结束,但是我仍然没有看到引用的相关性,因为它是关于从一个函数。我的猜测是,RVO 是您看不到 destruction 临时内部 f 的原因

标签: c++ reference language-lawyer lifetime temporary


【解决方案1】:

所以看起来寿命延长了。

代码非常有效,但请注意,延长生命周期的对象不是Test{}在函数f()中创建的临时对象,而是函数f()返回的对象。返回的对象是从临时对象移动构造的,然后绑定到 t 并延长生命周期。 BTW返回的对象是按值返回的,也是临时的。

为了观察,您可以手动添加移动构造函数:

struct Test
{
  Test() { std::cout << "creation\n"; }
  ~Test() { std::cout << "destruction\n"; }
  Test(Test&&) { std::cout << "move\n"; }
};

并以禁止copy elision模式编译运行,结果为:

before f call
creation      // the temporary created inside f
move          // return object move-constructed
destruction   // the temporary destroyed
after f call
destruction   // the returned object destroyed

LIVE


引用标准,§15.2/6 Temporary objects [class.temporary]

引用绑定的临时对象或被绑定的临时对象 引用绑定到的子对象的完整对象 在引用的生命周期内持续存在,除了:

(6.1) 绑定到函数中引用参数的临时对象 调用一直持续到包含的完整表达式完成 来电。

(6.2) 临时绑定到返回值的生存期 函数返回语句未扩展;临时被毁 在 return 语句中的完整表达式的末尾。

(6.3) 临时绑定到新初始化程序中的引用仍然存在 直到包含 新的初始化程序。 [ 示例:

struct S { int mi; const std::pair<int,int>& mp; };
S a { 1, {2,3} };
S* p = new S{ 1, {2,3} };   // Creates dangling reference

 — 结束示例 ] [ 注意:这可能会引入悬空引用,并且 鼓励实现在这种情况下发出警告。 — 尾注 ]

【讨论】:

  • 好的,所以临时(不完全是在 f 中创建的,我知道)的生命周期正在延长,是吗?您能否指出标准对这些事情的规定?
  • @Criss 是的。并且标准所说的和cppreference.com一样;无论如何,我添加到答案中。
  • 好答案 - 但请注意,由于 C++17 复制省略是强制性的,因此不能调用复制或移动构造函数。但是寿命还是会延长的。
  • 为什么临时对象销毁后移动的对象也没有销毁?
  • @Herrgott 你指的是哪个对象?
【解决方案2】:

引用GOTW article

临时对象只持续到它出现的完整表达式的结尾。但是,C++ 故意指定将临时对象绑定到堆栈上对 const(或 ravlue 引用)的引用会延长临时对象的生命周期到引用本身的生命周期,从而避免常见的悬空引用错误.

string f() { return "abc"; }

void g() {
const string& s = f();
  cout << s << endl;    // can we still use the "temporary" object?
}

在上面的示例中,f() 返回的临时值一直存在到右花括号为止。 (请注意,这仅适用于基于堆栈的引用。它不适用于作为对象成员的引用。)

法律术语请阅读this SO answer

答案适用于本地常量引用和右值引用

【讨论】:

  • 恕我直言,该功能的存在有点令人困惑(也在问题中)。如果是const string&amp; s = "abc"; 会不会是完全相同的情况和推理?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-20
  • 1970-01-01
  • 2015-04-11
  • 2019-01-06
相关资源
最近更新 更多