为什么临时对象的生命周期不会延长到封闭对象的生命周期?

发布于 2024-11-27 23:34:48 字数 1176 浏览 0 评论 0原文

我知道临时不能绑定到非常量引用,但它可以绑定到const引用。也就是说,

 A & x = A(); //error
 const A & y = A(); //ok

我也知道在第二种情况(上面)中,从 A() 创建的临时对象的生命周期一直延伸到 const 引用的生命周期(即 y )。

但我的问题是:

绑定到临时对象的 const 引用是否可以进一步绑定到另一个 const 引用,从而将临时对象的生命周期延长到第二个对象的生命周期?

我尝试了这个,但没有成功。我不太明白这一点。我写了这段代码:

struct A
{
   A()  { std::cout << " A()" << std::endl; }
   ~A() { std::cout << "~A()" << std::endl; }
};

struct B
{
   const A & a;
   B(const A & a) : a(a) { std::cout << " B()" << std::endl; }
   ~B() { std::cout << "~B()" << std::endl; }
};

int main() 
{
        {
            A a;
            B b(a);
        }
        std::cout << "-----" << std::endl;
        {
            B b((A())); //extra braces are needed!
        }
}

输出(ideone):

 A()
 B()
~B()
~A()
-----
 A()
 B()
~A()
~B()

输出差异?为什么在第二种情况下,临时对象A()会先于对象b被销毁?标准 (C++03) 是否讨论了这种行为?

I know that a temporary cannot be bound to a non-const reference, but it can be bound to const reference. That is,

 A & x = A(); //error
 const A & y = A(); //ok

I also know that in the second case (above), the lifetime of the temporary created out of A() extends till the lifetime of const reference (i.e y).

But my question is:

Can the const reference which is bound to a temporary, be further bound to yet another const reference, extending the lifetime of the temporary till the lifetime of second object?

I tried this and it didn't work. I don't exactly understand this. I wrote this code:

struct A
{
   A()  { std::cout << " A()" << std::endl; }
   ~A() { std::cout << "~A()" << std::endl; }
};

struct B
{
   const A & a;
   B(const A & a) : a(a) { std::cout << " B()" << std::endl; }
   ~B() { std::cout << "~B()" << std::endl; }
};

int main() 
{
        {
            A a;
            B b(a);
        }
        std::cout << "-----" << std::endl;
        {
            B b((A())); //extra braces are needed!
        }
}

Output (ideone):

 A()
 B()
~B()
~A()
-----
 A()
 B()
~A()
~B()

Difference in output? Why the temporary object A() is destructed before the object b in the second case? Does the Standard (C++03) talks about this behavior?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

ま柒月 2024-12-04 23:34:48

该标准考虑了两种延长临时生命周期的情况:

§12.2/4 有两种上下文,其中临时变量在与完整表达式末尾不同的点被销毁。第一个上下文是当表达式作为定义对象的声明符的初始值设定项出现时。在这种情况下,保存表达式结果的临时变量将持续存在,直到对象的初始化完成。 [...]

§12.2/5 第二个上下文是引用绑定到临时对象时。 [...]

这两个都不允许您通过稍后将引用绑定到另一个 const 引用来延长临时引用的生命周期。但忽略标准并想想正在发生的事情:

临时对象是在堆栈中创建的。好吧,从技术上讲,调用约定可能意味着适合寄存器的返回值(临时)甚至可能不会在堆栈中创建,但请耐心等待。当您将常量引用绑定到临时变量时,编译器在语义上会创建一个隐藏的命名变量(这就是为什么复制构造函数需要可访问,即使它没有被调用)并将引用绑定到该变量。是否实际创建或删除副本是一个细节:我们拥有的是一个未命名局部变量和对它的引用。

如果标准允许您的用例,那么这意味着临时变量的生命周期必须一直延长,直到最后一次引用该变量。现在考虑示例的这个简单扩展:

B* f() {
   B * bp = new B(A());
   return b;
}
void test() {
   B* p = f();
   delete p;
}

现在的问题是临时变量(我们称其为 _T)绑定在 f() 中,它的行为就像那里的局部变量。该引用绑定在*bp内部。现在该对象的生命周期超出了创建临时对象的函数,但因为 _T 不是动态分配的,所以这是不可能的。

您可以尝试并推理出在此示例中延长临时对象的生命周期所需的工作量,答案是如果没有某种形式的 GC,就无法完成此任务。

The standard considers two circumstances under which the lifetime of a temporary is extended:

§12.2/4 There are two contexts in which temporaries are destroyed at a different point than the end of the fullexpression. The first context is when an expression appears as an initializer for a declarator defining an object. In that context, the temporary that holds the result of the expression shall persist until the object’s initialization is complete. [...]

§12.2/5 The second context is when a reference is bound to a temporary. [...]

None of those two allow you to extend the lifetime of the temporary by a later binding of the reference to another const reference. But ignore the standarese and think of what is going on:

Temporaries are created in the stack. Well, technically, the calling convention might mean that a returned value (temporary) that fits in the registers might not even be created in the stack, but bear with me. When you bind a constant reference to a temporary the compiler semantically creates a hidden named variable (that is why the copy constructor needs to be accessible, even if it is not called) and binds the reference to that variable. Whether the copy is actually made or elided is a detail: what we have is an unnamed local variable and a reference to it.

If the standard allowed your use case, then it would mean that the lifetime of the temporary would have to be extended all the way until the last reference to that variable. Now consider this simple extension of your example:

B* f() {
   B * bp = new B(A());
   return b;
}
void test() {
   B* p = f();
   delete p;
}

Now the problem is that the temporary (lets call it _T) is bound in f(), it behaves like a local variable there. The reference is bound inside *bp. Now that object's lifetime extends beyond the function that created the temporary, but because _T was not dynamically allocated that is impossible.

You can try and reason the effort that would be required to extend the lifetime of the temporary in this example, and the answer is that it cannot be done without some form of GC.

落日海湾 2024-12-04 23:34:48

不,延长的寿命不会通过传递参考而进一步延长。

在第二种情况下,临时变量绑定到参数 a,并在参数生命周期结束时(构造函数结束时)销毁。

标准明确指出:

构造函数构造函数初始化程序 (12.6.2) 中引用成员的临时绑定将持续存在,直到构造函数退出。

No, the extended lifetime is not further extended by passing the reference on.

In the second case, the temporary is bound to the parameter a, and destroyed at the end of the parameter's lifetime - the end of the constructor.

The standard explicitly says:

A temporary bound to a reference member in a constructor's ctor-initializer (12.6.2) persists until the constructor exits.

妞丶爷亲个 2024-12-04 23:34:48

§12.2/5 说“第二个上下文[当临时的生命周期
被扩展]是指将引用绑定到临时对象。”

从字面上看,这清楚地表明寿命应该延长
你的情况;你的 B::a 肯定绑定到一个临时变量。 (参考
绑定到一个对象,并且我看不到它可能存在的任何其他对象
)然而,这是非常糟糕的措辞;我确信那是什么
意思是“第二个上下文是当临时用于
初始化一个引用”,并且延长的生命周期对应于
使用右值表达式创建的引用初始化的值
临时的,而不是以后可能的任何其他参考文献
被绑定到对象上。就目前而言,措辞需要一些东西
这根本无法实现:考虑:

void f(A const& a)
{
    static A const& localA = a;
}

用以下方式调用:

f(A());

编译器应该将 A() 放在哪里(假设它通常看不到
f() 的代码,并且不知道何时的本地静态
生成呼叫)?

事实上,我认为这值得 DR。

我可能会补充一点,有文字强烈表明我的
意图的解释是正确的。想象一下你有一秒钟
B 的构造函数:

B::B() : a(A()) {}

在这种情况下,B::a 将直接使用临时变量进行初始化;这
即使按照我的解释,这个临时的生命周期也应该延长。
然而,该标准对这种情况做出了特定的例外;这样一个
临时仅持续到构造函数退出为止(这又会
给你留下一个悬而未决的参考)。这个例外提供了一个非常
强烈表明该标准的作者无意
类中的成员引用可延长任何临时对象的生命周期
他们有义务;再次,动机是可实施性。想象
而不是

B b((A()));

你写的:

B* b = new B(A());

编译器应该将临时 A() 放置在哪里,以便它的生命周期
是动态分配的B吗?

§12.2/5 says “The second context [when the lifetime of a temporary
is extended] is when a reference is bound to a temporary.”
Taken
literally, this clearly says that the lifetime should be extended in
your case; your B::a is certainly bound to a temporary. (A reference
binds to an object, and I don't see any other object it could possibly
be bound to.) This is very poor wording, however; I'm sure that what is
meant is “The second context is when a temporary is used to
initialize a reference,”
and the extended lifetime corresponds to
that of the reference initiailized with the rvalue expression creating
the temporary, and not to that of any other references which may later
be bound to the object. As it stands, the wording requires something
that simply isn't implementable: consider:

void f(A const& a)
{
    static A const& localA = a;
}

called with:

f(A());

Where should the compiler put A() (given that it generally cannot see
the code of f(), and doesn't know about the local static when
generating the call)?

I think, actually, that this is worth a DR.

I might add that there is text which strongly suggests that my
interpretation of the intent is correct. Imagine that you had a second
constructor for B:

B::B() : a(A()) {}

In this case, B::a would be directly initialized with a temporary; the
lifetime of this temporary should be extended even by my interpretation.
However, the standard makes a specific exception for this case; such a
temporary only persists until the constructor exits (which again would
leave you with a dangling reference). This exception provides a very
strong indication that the authors of the standard didn't intend for
member references in a class to extend the lifetime of any temporaries
they are bound to; again, the motivation is implementability. Imagine
that instead of

B b((A()));

you'd written:

B* b = new B(A());

Where should the compiler put the temporary A() so that it's lifetime
would be that of the dynamically allocated B?

旧城烟雨 2024-12-04 23:34:48

您的示例不执行嵌套生命周期扩展

在构造函数中

B(const A & a_) : a(a_) { std::cout << " B()" << std::endl; }

这里的 a_ (为便于说明而重命名)不是临时的。表达式是否是临时的是表达式的语法属性,并且 id-expression 永远不是临时的。所以这里不会发生寿命延长。

这是一种会发生生命周期延长的情况:

B() : a(A()) { std::cout << " B()" << std::endl; }

但是,由于引用是在构造函数初始化程序中初始化的,因此生命周期只会延长到函数结束为止。每 [class.temporary]p5

到构造函数的构造函数初始化程序 (12.6.2) 中的引用成员的临时绑定将持续存在,直到构造函数退出。

在对构造函数的调用

B b((A())); //extra braces are needed!

,我们将引用绑定到临时对象。 [class.temporary]p5 说:

在函数调用 (5.2.2) 中临时绑定到引用参数将持续存在,直到包含调用的完整表达式完成为止。

因此 A 临时变量在语句结束时被销毁。这发生在块末尾的 B 变量被销毁之前,解释了您的日志输出。

其他情况确实执行嵌套生命周期扩展

聚合变量初始化

具有引用成员的结构的聚合初始化可以延长生命周期:

struct X {
  const A &a;
};
X x = { A() };

在这种情况下,A 临时文件直接绑定到引用,因此临时文件的生命周期被延长到xa的生​​命周期,这与x的生​​命周期相同。 (警告:直到最近,很少有编译器能做到这一点)。

聚合临时初始化

在 C++11 中,您可以使用聚合初始化来初始化临时变量,从而获得递归生命周期扩展:

struct A {
   A()  { std::cout << " A()" << std::endl; }
   ~A() { std::cout << "~A()" << std::endl; }
};

struct B {
   const A &a;
   ~B() { std::cout << "~B()" << std::endl; }
};

int main() {
  const B &b = B { A() };
  std::cout << "-----" << std::endl;
}

使用 trunk Clang 或 g++,这会产生以下输出:

 A()
-----
~B()
~A()

请注意,< code>A 临时和 B 临时是生命周期延长的。因为 A 临时对象的构造首先完成,所以它最后被销毁。

std::initializer_list 初始化中,

C++11 的 std::initializer_list 执行生命周期扩展,就好像通过绑定对底层数组的引用。因此,我们可以使用 std::initializer_list 执行嵌套生命周期扩展。然而,编译器错误在这个领域很常见:

struct C {
  std::initializer_list<B> b;
  ~C() { std::cout << "~C()" << std::endl; }
};
int main() {
  const C &c = C{ { { A() }, { A() } } };
  std::cout << "-----" << std::endl;
}

使用 Clang trunk 生成:

 A()
 A()
-----
~C()
~B()
~B()
~A()
~A()

和使用 g++ trunk:

 A()
 A()
~A()
~A()
-----
~C()
~B()
~B() 

这些都是错误的;正确的输出是:

 A()
 A()
-----
~C()
~B()
~A()
~B()
~A()

Your example doesn't perform nested lifetime extension

In the constructor

B(const A & a_) : a(a_) { std::cout << " B()" << std::endl; }

The a_ here (renamed for exposition) is not a temporary. Whether an expression is a temporary is a syntactic property of the expression, and an id-expression is never a temporary. So no lifetime extension occurs here.

Here's a case where lifetime-extension would occur:

B() : a(A()) { std::cout << " B()" << std::endl; }

However, because the reference is initialized in a ctor-initializer, the lifetime is only extended until the end of the function. Per [class.temporary]p5:

A temporary bound to a reference member in a constructor's ctor-initializer (12.6.2) persists until the constructor exits.

In the call to the constructor

B b((A())); //extra braces are needed!

Here, we are binding a reference to a temporary. [class.temporary]p5 says:

A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.

Therefore the A temporary is destroyed at the end of the statement. This happens before the B variable is destroyed at the end of the block, explaining your logging output.

Other cases do perform nested lifetime extension

Aggregate variable initialization

Aggregate initialization of a struct with a reference member can lifetime-extend:

struct X {
  const A &a;
};
X x = { A() };

In this case, the A temporary is bound directly to a reference, and so the temporary is lifetime-extended to the lifetime of x.a, which is the same as the lifetime of x. (Warning: until recently, very few compilers got this right).

Aggregate temporary initialization

In C++11, you can use aggregate initialization to initialize a temporary, and thus get recursive lifetime extension:

struct A {
   A()  { std::cout << " A()" << std::endl; }
   ~A() { std::cout << "~A()" << std::endl; }
};

struct B {
   const A &a;
   ~B() { std::cout << "~B()" << std::endl; }
};

int main() {
  const B &b = B { A() };
  std::cout << "-----" << std::endl;
}

With trunk Clang or g++, this produces the following output:

 A()
-----
~B()
~A()

Note that both the A temporary and the B temporary are lifetime-extended. Because the construction of the A temporary completes first, it is destroyed last.

In std::initializer_list<T> initialization

C++11's std::initializer_list<T> performs lifetime-extension as if by binding a reference to the underlying array. Therefore we can perform nested lifetime extension using std::initializer_list. However, compiler bugs are common in this area:

struct C {
  std::initializer_list<B> b;
  ~C() { std::cout << "~C()" << std::endl; }
};
int main() {
  const C &c = C{ { { A() }, { A() } } };
  std::cout << "-----" << std::endl;
}

Produces with Clang trunk:

 A()
 A()
-----
~C()
~B()
~B()
~A()
~A()

and with g++ trunk:

 A()
 A()
~A()
~A()
-----
~C()
~B()
~B() 

These are both wrong; the correct output is:

 A()
 A()
-----
~C()
~B()
~A()
~B()
~A()
情定在深秋 2024-12-04 23:34:48

在第一次运行中,对象按照它们被推入堆栈的顺序被销毁 ->即push A、push B、pop B、pop A。

在第二次运行中,A 的生命周期随着b 的构造而结束。因此,它创建了 A,从 A 创建了 B,A 的生命周期结束,因此它被销毁,然后 B 被销毁。有道理...

In your first run, the objects are destroyed in the order they were pushed on the stack -> that is push A, push B, pop B, pop A.

In the second run, A's lifetime ends with the construction of b. Therefore, it creates A, it creates B from A, A's lifetime finishes so it is destroyed, and then B is destroyed. Makes sense...

甜中书 2024-12-04 23:34:48

我不了解标准,但可以讨论我在之前几个问题中看到的一些事实。

第一个输出保持原样,原因显而易见,ab 位于同一范围内。另外,ab 之后被销毁,因为它是在 b 之前构造的。

我认为您应该对第二个输出更感兴趣。在开始之前,我们应该注意以下类型的对象创建(独立临时对象):

{
  A();
}

仅持续到下一个 ; 并且不适用于它周围的块演示。在第二种情况下,当您

B b((A()));

这样做时,一旦 B() 对象创建完成,A() 就会被销毁。由于 const 引用可以绑定到临时引用,因此不会出现编译错误。然而,如果您尝试访问 B::a ,它肯定会导致逻辑错误,它现在绑定到已经超出范围的变量。

I don't know about standards, but can discuss some facts which I saw in few previous questions.

The 1st output is as is for obvious reasons that a and b are in the same scope. Also a is destroyed after b because it's constructed before b.

I assume that you should be more interested in 2nd output. Before I start, we should note that following kind of object creations (stand alone temporaries):

{
  A();
}

last only till the next ; and not for the block surrounding it. Demo. In your 2nd case, when you do,

B b((A()));

thus A() is destroyed as soon as the B() object creation finishes. Since, const reference can be bind to temporary, this will not give compilation error. However it will surely result in logical error if you try to access B::a, which is now bound to already out of scope variable.

蓦然回首 2024-12-04 23:34:48

§12.2/5 说

在函数调用 (5.2.2) 中临时绑定到引用参数将持续存在,直到包含调用的完整表达式完成为止。

真的,剪得很漂亮,晒干了。

§12.2/5 says

A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full expression containing the call.

Pretty cut and dried, really.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文