在复习OOP的时候发现一个有关函数调用和析构的怪异问题,下面是代码:
#include <iostream>
using namespace std;
class ctest
{
public:
ctest(int x=0) : a(x) { cout << "构造对象(" << a << ")\n"; }
~ctest(){ cout << "析构对象(" << a << ")\n";}
ctest & Add() // 本成员函数为引用返回
{
++a;
return *this;
}
ctest add() // 本成员函数为值返回
{
ctest temp(*this);
++a;
return temp;
}
friend ostream & operator<<(ostream &out, const ctest &c)
{
out << c.a;
return out;
}
private:
int a;
};
int main()
{
ctest a(100), b(200);
cout << a << ", " << b << endl; // 第3行输出
a.Add().Add();
b.add().add().add().add().add(); // 拷贝构造临时无名对象时无输出
cout << a << ", " << b << endl; // 第6行输出
a.~ctest(); // 主动调用析构函数,并不销毁对象
b.~ctest(); // 主动调用析构函数,并不销毁对象
cout << a << ", " << b << endl; // 对象a,b仍可被访问。第9行输出
cout << "返回操作系统。" << endl; // 第10行输出
return 0;
}
问题出在哪里呢?
就是在主函数第四行b对add()反复调用的过程中发现的问题,具体什么情况呢?
按照我们常规思考,应该是正常析构,比如200,201,202,203,204……
可以看到,析构对象第一次200后续都是201,为什么呢?
嗯……不知道程序各项函数怎么调用的时候,GDB是个好东西!我们打断点调试一下吧!
好的,我们开始调试了,可以看到a是102,b现在还是200,正常
进入其中
ok我们可以看到temp在还没拷贝的时候随机被分配一个数据成员的值,this就是我们外面的b,a的值是200,正常
记住this的地址0x5ffe34
ok, 下一步,++a之前temp已经拷贝构造完成了,a变成了200
在debug console中输入&temp,得到地址0x5ffe4c
++a,*this的a变成了201,正常
return temp
欸?!问题来了,终端没有析构信息出现,this的地址变成了0x5ffe4c,并且值是200,正好是之前的temp。
其实这也在我们预料中,返回的是temp,先前没对temp做修改,改的是*this,这个时候改的*this其实才是先前 return 的 temp。
再继续,直接展示本次调用函数结束
进入下一次调用,地址变成0x5ffe48
ok,此时情况大致明了了,我们直接跳出这个语句
跳出之后,析构函数以先进先出的队列形式调用输出析构对象的情况。
大致想通了,首先,我们的b调用add(),注意b在这段代码中是不会被析构的!还有我们的析构调用一定是按照栈的先进后出形式调用的!
其实这样一看就很明了了
首先b拷贝构造一个temp(200),随后b的a+1 = 201,返回temp,之后不管b了,此时temp调用add()
temp作为this,拷贝构造一个新的temp(200),旧的temp的a+1 = 201,随后返回新的temp,新的temp,旧的temp放在函数调用临时堆栈的底部,值为201,在所有函数调用结束前,都不会再管它。
随后我们接着反复这个过程。
最后一步略有不同,因为不会再利用返回的temp调用新的add(),因此直接return放在栈顶。
函数调用结束,从栈顶到栈底依次调用析构函数
输出情况正好就是200,201,201,201,201!
完美解决