【问题标题】:Extending temporary's lifetime, works with block-scoped aggregate, but not through `new`; why?延长临时的生命周期,适用于块范围聚合,但不能通过 `new`;为什么?
【发布时间】:2014-05-31 13:23:24
【问题描述】:

注意:这个问题最初是由 Ryan Haining this answera comment 提出的。


struct A { std::string const& ref; };

// (1)

A a { "hello world" };              // temporary's lifetime is extended to that of `a`
std::cout << a.ref << std::endl;    // safe

// (2)

A * ptr = new A { "hello world" };  // lifetime of temporary not extended?
std::cout << ptr->ref << std::endl; // UB: dangling reference


问题

  • 为什么在(1)中延长了临时的生命周期,而在(2)中却没有?

【问题讨论】:

  • 投反对票的人能否解释一下为什么这个问题值得-1
  • @FilipRoséen-refp:至少有一个人,活跃在c++ 标签中,经常对任何自我回答的问题投反对票。我很清楚它是谁,这很烦人。

标签: c++ c++11 language-lawyer object-lifetime


【解决方案1】:

长话短说

编译器无法延长 new A { "temporary " } 中涉及的临时文件的生命周期,因为创建的 A 和临时文件具有不同的存储期限

可以在本文末尾找到对标准内容的参考。标准明确表示生命周期不会延长,但没有详细说明为什么会这样。

这篇文章将尝试以更广泛的受众可以理解的方式解释原因,而不是仅平均language-lawyer


简介

在 C++ 中,一个对象可以有几种不同的存储持续时间,其中有自动-动态存储持续时间,解释说简述如下:

自动存储时长

具有自动存储持续时间的对象的存储将持续到创建它们的块退出。

  • 在块作用域中声明的对象具有自动存储持续时间(除非它们被声明为 staticextern,但不是 register)。

  • 根据定义,临时对象是在块范围内声明的,因此它们也具有自动存储持续时间


动态存储时长

具有动态存储持续时间的对象的存储将持续存在,直到明确声明它应该被释放;换句话说,这种存储不受任何特定范围的约束。

  • 通过operator new 动态创建的对象具有,如提示的那样,动态存储持续时间

    存储将持续存在,直到对 operator delete 进行匹配调用。




具有自动存储持续时间的聚合初始化

如上一节所述,临时具有自动存储期限

如果我们用自动存储持续时间构造一个聚合,这也将存储绑定到当前范围;这意味着 temporary 的生命周期可以很容易地延长以匹配 aggregate 的生命周期。

注意:我们可以想象他们生活在同一个“盒子”中,在作用域结束时我们丢弃这个盒子 em>,这很好;无论是临时的还是 聚合,都不会超过 box 的生命周期。


我们的实施 (A)

struct A { std::string const& ref; };

void func () {
  A x { {"hello world"} };
}

幕后 (A)

由于x和temporary都有自动存储持续时间,编译器可以实现如下功能,语义等价,sn-p:

void  __func () {
  std::string __unnamed_temporary { "hello world" };
  A x { __unnamed_temporary };
}

注意:临时和聚合的生命周期都绑定到当前范围,太棒了!



聚合初始化与动态存储持续时间

我们的实施 (B)

A* gunc () {
  A *    ptr = new A { { "hello world" } };
  return ptr;
}

int main () {
  A * p = gunc ();

  std::cout << p->ref << std::endl; // DANGER, WILL ROBINSON!

  delete p;
}

在前面的部分中已经说过,临时对象具有自动存储持续时间,这意味着绑定到A::ref 的临时对象将在驻留在当前范围内的存储上构建。


幕后花絮 (B)

gunc 的语义等价可以如下实现:

A* gunc () {
  A __unnamed_temporary { "hello world " };

  A * ptr = new A { __unnamed_temporary }; // (1)

  return ptr;
}

你也是这么想的,不是吗?

我们不能再延长临时文件的生命周期以匹配在 (1) 处使用 动态存储持续时间 创建的 A 的生命周期。

问题是__unnamed_temporary 的自动存储将在我们从gunc 返回后立即消失,从而有效地杀死我们的临时对象。

但是,动态创建的 A 仍然存在,给我们留下了 main 中的悬空引用。



结论

编译器无法延长通过 new-initializer 创建对象时涉及的任何临时对象的生命周期,因为 newed 对象和临时对象将具有不同的存储期限。



标准 (n3797) 怎么说?

12.2p5 临时对象 [class.temporary]

引用绑定到的临时对象或作为引用绑定到的子对象的完整对象的临时对象在引用的生命周期内持续存在,但以下情况除外:

...

  • new-initializer (5.3.4) 中对引用的临时绑定一直存在,直到包含 new-initializer 的完整表达式完成为止。 p>

    [ 注意:这可能会引入悬空引用,鼓励实现在这种情况下发出警告。 -- 尾注 ]

【讨论】:

  • 解决方案是将ref更改为std::unique_ptr&lt;std::string&gt; ptr并将其初始化为new A { std::make_unique&lt;std::string&gt;("hello world") };吗?如果有很多,这对于初始化每个引用是不直观的。
  • @0x499602D2 不会通过将任何对象绑定到引用来延长其生命周期,但在您的 sn-p 中不会有悬空引用/指针;所以可以说这是一种解决方案。
猜你喜欢
  • 2014-07-16
  • 2019-01-06
  • 1970-01-01
  • 1970-01-01
  • 2021-09-20
  • 2018-11-13
  • 2020-03-29
  • 2016-05-20
相关资源
最近更新 更多