考虑以下代码:
#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
。
调用return
时,堆栈对象仍在作用域中,因此i
的值仍为3,因为尚未调用A
的析构函数。当函数的堆栈展开时,堆栈对象将被移除,这是在设置了返回值之后的。
想象一下,如果不是这样的话,如果堆栈对象可以在return
期间被销毁。如何从函数中返回本地值?
对评论的回应
@帕迪,在这种情况下,你能解释func2和func3吗?
从表面上看,func2
看起来几乎与func1
完全相同,如果您认为它应该返回8,那是情有可原的。但这里的区别在于它返回int&
而不是int
。这意味着return i;
返回对i
的引用。即使当堆栈开始展开时i
是8,但当obj
被销毁并返回值被弹出给调用者时,i
的值是10。因为我们返回了一个引用,所以返回值被取消引用,并使用i
(10)的当前值。
对于func3
来说,这更容易。这返回一个正常的int
,就像func1
一样。但是实例A obj;
在它自己的块范围内:{ A obj; }
。因此,它在return
之前被销毁,当我们从函数返回时,i
的值为10。
来自标准(C++11,3.7.3):
(1) 显式声明为register或未显式声明的static或extern的块作用域变量具有自动存储持续时间。这些实体的存储将持续到创建它们的块退出为止。
[…]
(3) 如果具有自动存储持续时间的变量具有初始化或具有副作用的析构函数,则不应在其块结束之前被销毁,也不得作为优化而将其消除,即使它看起来未使用,除非类对象或其复制/移动可以按照12.8的规定删除。
这意味着A
的寿命在声明它的块结束的地方结束。在func1
的情况下,这是在return语句之后。在func3
的情况下,它位于return语句之前。
("块"是一段用大括号括起来的代码:{...}
。)
因此,func1
在调用A
的析构函数之前评估返回值,因此返回3
。func3
在析构函数被调用后计算返回值,因此返回10
。
在func2
的情况下,顺序与func1
中的顺序相同,但因为它返回引用,所以A
的析构函数执行的值修改,即使是在评估返回值之后执行的,也会对返回的值产生影响。
它与在调用a析构函数之前返回i的副本还是对它的引用有关:
func1()情况:
- 我设定为3
- i的值作为i的副本返回(作为临时)
- 在析构函数中i被设置为10
- i(3)的副本已打印
func2()情况:
- 我被调到8
- i的值作为对全局变量i的引用返回
- 在析构函数中i被设置为10
- 打印i的当前值
func3()情况:
- 我被调到8
- 在析构函数中i被设置为10
- i的值作为副本返回
- i(10)的副本已打印出来
析构函数出现在return语句之后,但在调用函数的下一行之前。返回值保存在隐藏变量中,然后调用析构函数和其他函数清理(例如返回堆栈),然后在调用方中继续执行。
为了进一步澄清——假设回流管线为return i+A.foo();
。直到这一行之后,你才想调用析构函数,或者A对调用foo无效。这就是为什么析构函数总是在返回之后调用的原因。
变量"i"在整个代码中都是全局的,没有它的"本地"实例。
对象超出作用域时会被销毁,这是在函数中的"return"之后,而不是之前。因此,只有在返回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.
如果你想返回"i",而不是它在表达式"return 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
请参阅http://ideone.com/XXYu2u
我强烈建议您在使用调试器之前和之后浏览此程序,以更好地熟悉整个过程-这是巩固您对正在发生的事情的理解的最佳方式。