请考虑以下代码:
#include <iostream>
#include <stdexcept>
using namespace std;
int i;
class A{
public:
~A(){i=10;}
};
int func1()
{
i=3;
A Ob; // -> here local object , hence created and destroyed
return i;
}
int& func2()
{
i=8;
A obj;
return i;
}
int func3()
{
i=8;
{A obj;}
return i;
}
int main()
{
cout << "i : " <<func1() << endl;
cout << "i : " <<func2() << endl;
cout << "i : " <<func3() << endl;
return(0);
}
输出:
$ ./TestCPP
i : 3
i : 10
i : 10
有人可以解释为什么我先是3岁吗?在func1()
中,A Ob
是局部变量,因此它被创建和销毁。当它被销毁时,它将调用其析构函数将i修改为10
,我期望i
为10
,但答案显示为i : 3
。
答案 0 :(得分:4)
当您调用return
时,堆栈对象仍在范围内,因此i
的值仍为3,因为尚未调用A
的析构函数。当函数的堆栈展开时,将删除堆栈对象,在设置了返回值之后。
想象一下,如果不是这种情况,是否可以在return
期间销毁堆栈对象。你怎么能从函数中返回一个本地值?
对评论的回应
@paddy在这种情况下你可以解释一下func2和func3吗?
从表面上看,func2
看起来与func1
看起来几乎完全相同,如果您认为它应该返回8,那么您会被原谅。但不同之处在于它返回int&
而不是int
i
。这意味着return i;
会返回i
的引用。尽管当堆栈开始展开时obj
为8,但是当i
被销毁并且返回值弹回到调用者时,i
的值为10.因为我们返回了引用,返回值被解除引用,并使用func3
(10)的当前值。
对于int
,它更容易。这会返回正常func1
,就像A obj;
一样。但实例{ A obj; }
位于其自己的块范围内:return
。因此它在i
之前被销毁,当我们从函数返回时,{{1}}的值为10。
答案 1 :(得分:2)
从标准(C ++ 11,3.7.3):
(1)显式声明寄存器或未显式声明为static或extern的块作用域变量具有自动存储持续时间。这些实体的存储将持续到创建它们的块退出。
[...]
(3)如果具有自动存储持续时间的变量具有初始化或具有副作用的析构函数,则不应该 在它的块结束之前被销毁,即使它看起来也不会作为优化被消除 不使用,但可以按照12.8中的规定消除类对象或其复制/移动。
这意味着A
的生命周期在声明它的块结束时结束。在func1
的情况下,这是在return语句之后。在func3
的情况下,它在return语句之前。
(“块”是用大括号括起来的一段代码:{...}
。)
因此,func1
在调用A
的析构函数之前计算返回值,因此返回3
。 func3
在调用析构函数后计算返回值,因此返回10
。
在func2
的情况下,顺序与func1
中的顺序相同,但因为它返回引用,所以由A
的析构函数执行的值修改,即使它在评估返回值后执行,对返回的值有影响。
答案 2 :(得分:2)
它与您是否在调用析构函数之前返回i的副本或对它的引用有关:
func1()案例:
func2()案例:
func3()案例:
答案 3 :(得分:0)
析构函数发生在return语句之后但在调用函数的下一行之前。返回值保存在隐藏变量中,然后调用析构函数和其他函数清理(例如返回堆栈),然后在调用者中继续执行。
进一步澄清 - 假设回报线为return i+A.foo();
。您不希望在此行之后调用析构函数,或者A无法调用foo。这就是为什么在返回后总是调用析构函数的原因。
答案 4 :(得分:0)
变量“i”在整个代码中都是全局的,没有“本地”实例。
当对象超出范围时会被销毁,在之后在你的函数中“返回”,而不是在它之前。因此,只有在返回i的第一个值并且函数结束后才会调用第一个dtor。
i = 3; <-- sets i to 3
A Ob; <-- constructs object.
return i; <-- places the value "3" in the return value register.
} <-- destroys object changing future values of "i" to 10.
如果你想在表达“return i”时返回“i”而不是它包含的值,则必须进行以下更改:
int& func1()
{
i = 3; <-- sets i to 3
A Ob; <-- constructs object.
return i; <-- places a reference to "i" in the return value register.
} <-- destroys object changing the value of "i" to 10
我强烈建议您在调试器之前和之后使用此程序,以便更好地熟悉整个过程 - 这是巩固您对正在发生的事情的理解的最佳方式。