注册 登录
编程论坛 C++教室

[讨论]关于析构函数的调用顺序

幽园香客 发布于 2007-03-19 16:26, 2428 次点击

如题,在调用析构函数时,其顺序和构造函数的顺序完全相反。例如:
class A
{
public:
A(){cout<<"A construction"<<endl;}
~A(){cout<<"A destruction"<<endl;}
};
class B
{
public:
B(){cout<<"B construction"<<endl;}
~B(){cout<<"B destruction"<<endl;}
A a;
};

main()
{
B b;
}
则其结果如下:A construction
B construction
B destruction
A destruction
结果验证了上述结论,可是问题就在后两个。
“析构函数只是在类对象生命结束时,由系统调用”---钱能 《C++程序设计教程》
我就搞不清楚了,为什么B对象结束比A对象还早?明明是在B对象中调用A的对象,B先被析构?一旦B被析构,岂不是B对象的一些资源都被释放掉了(假如有的话)?还有如果从调用机理来说,也是说不通的。哪位兄弟知道,到底为什么?谢谢了

[此贴子已经被作者于2007-3-19 16:27:10编辑过]

15 回复
#2
浮0云2007-03-19 18:16
我发表一下看法:我觉得B对象先被析构是合理的,假设A对象先被析构,B对象还在运行,用到A对象时,又会调用A对象的构造函数;当B对象先被析构时,A对象自然没有用到的可能了。
#3
幽园香客2007-03-19 18:56

问题关键是:A对象是B类中成员,如果把B对象析构了,那么B对象中所拥有的一切资源都将失去。那么A对象怎么能还调用?

#4
song42007-03-19 19:14
.....
再说一边
构造好比分东西
先长辈,后朋友,最后自己
析构好比干活
先自己,后朋友,最后长辈
#5
幽园香客2007-03-19 21:12
楼上说的很形象,也很贴切。我翻阅了几本C++方面的书,都是只是提到析构函数的调用顺序,却总是没有讲为什么是这样。知其然,知其所以然!呵呵,或许,过于钻牛角尖了!不过,总还是希望能够得到一个合理的解释。

[此贴子已经被作者于2007-3-19 21:14:15编辑过]


#6
Arcticanimal2007-03-19 21:34

谈谈自己的看法
析构函数代码的执行是有一个过程的,
在这个过程中对象所占有的资源是被逐步释放的,并不是唰的一下全没了,(如果C++的析构函数有代码给我们看这个问题就会很清楚了
当派生类析构函数执行到 清除从基类继承来的对象占有的资源时 便调用基类的析构函数,
基类的析构函数执行完后返回派生类的析构函数继续执行剩余的代码。

#7
幽园香客2007-03-19 22:07
楼上的,其实如果按照上面你说的情况,我想结果更应该是先调用A对象的析构函数,再调用B的析构函数。因为,如果B中不止一个资源需要释放,当释放A对象所占资源时去调用A的析构函数;然后再调用B的析构了。按照我们通常考虑函数嵌套调用的方法去考虑这个问题时,就会得到和事实相反的结果。
#8
song42007-03-20 08:18
以下是引用幽园香客在2007-3-19 21:12:38的发言:
楼上说的很形象,也很贴切。我翻阅了几本C++方面的书,都是只是提到析构函数的调用顺序,却总是没有讲为什么是这样。知其然,知其所以然!呵呵,或许,过于钻牛角尖了!不过,总还是希望能够得到一个合理的解释。

很正常
构造顺序我就不说了
至于析构
你可以这么想
如果不销毁自己,而是父类
那么你delete 自身的语句不是有问题么
既然你写的是delete 自身
当然先从自己开始
另外
父类与子类在存储上是包容的关系
子类内部包含父类
先析构自身的成员
然后到了父类那里
//当然,这里父类的空间是子类空间的一部分
//可以想象到覆盖,因为你delete子类,所以实行子类的
//析构,而父类在子类中,所以当析构完成到父类那里调用父类
//至于你会问为什么不县析构父类,然后在析构子类成员呢
//他们都是子类一部分啊
//因为析构是分步的,根据构造顺序,我们可以知道父类是在最里面的
//指针需要一点一点上移动
//当然需要最后了
子类构造函数调用父类的析构函数
至于友类跟这个差不多

#9
song42007-03-20 08:19
以下是引用Arcticanimal在2007-3-19 21:34:41的发言:

谈谈自己的看法
析构函数代码的执行是有一个过程的,
在这个过程中对象所占有的资源是被逐步释放的,并不是唰的一下全没了,(如果C++的析构函数有代码给我们看这个问题就会很清楚了)//这个有办法实现吧
当派生类析构函数执行到 清除从基类继承来的对象占有的资源时 便调用基类的析构函数,
基类的析构函数执行完后返回派生类的析构函数继续执行剩余的代码。

#10
Arcticanimal2007-03-20 19:06
我是说系统自动生成的析构函数具体清除内存占用的代码,多谢斑竹指点

[此贴子已经被作者于2007-3-20 19:47:09编辑过]


#11
Arcticanimal2007-03-20 19:44
以下是引用幽园香客在2007-3-19 22:07:36的发言:
楼上的,其实如果按照上面你说的情况,我想结果更应该是先调用A对象的析构函数,再调用B的析构函数//此句不妥,怎么会是这样呢因为,如果B中不止一个资源需要释放,当释放A对象所占资源时去调用A的析构函数;然后再调用B的析构了//此处不应该是“再调用”,只是返回B的析构函数继续执行。按照我们通常考虑函数嵌套调用的方法去考虑这个问题时,就会得到和事实相反的结果。

#include<iostream.h>
class baseone
{
public:
baseone(){cout<<"baseone() called!\n";}
~baseone(){cout<<"~baseone() called!\n";}
};
class basetwo
{
public:
basetwo(){cout<<"basetwo() called!\n";}
~basetwo(){cout<<"~basetwo() called!\n";}
};
class derive
{
public:
derive(){cout<<"derive() called!\n";}
~derive(){cout<<"~derive() called!\n";one.~baseone();two.~basetwo();} //虽然显式调用基类析构函数,系统还是会再一次调用基类析构函数
baseone one;/////////
basetwo two;//改变 one 与two 的声明先后会导致系统自动调用 baseone 与 basetwo的析构函数顺序不同
};
void main()
{
derive der;
}
最终~basetwo()与~baseone()都调用了两次

#12
幽园香客2007-03-20 20:28
谢谢6楼和song4!经过这两天的讨论,我对析构函数又有了更进一步认识。
#13
江南孤峰2007-03-21 09:57

创建实例时,程序首先初始化对象的数据成员: A construction
调用构造函数: B construction
C++在释放对象前调用析构函数:B destruction
删除对象的一切数据: A destruction
通常对数据成员的处理(初始化或者删除)是在构造和析构函数里执行
但A是一个类成员,在B的构造函数调用前被自动调用,B在没有调用析构
函数前,B的数据成员都是有效的,B调用析构函数后 A的生命周期也随之
结束,因此A自动调用其自身的析构函数.

#14
幽园香客2007-03-24 22:27
这两天,又仔细琢磨了下。觉得问题关键应该是和栈有关。一个最简单的例子:
class CA
{ public:
CA(){cout<<"CA construction"<<endl;}
~CA(){cout<<"CA destruction"<<endl;}
};
class CB
{ public:
CB(){cout<<"CB construction"<<endl;}
~CB(){cout<<"CB destruction"<<endl;}
};
void main()
{
CA a;
CB b;
}
结果还是:CA construction
CB construction
CB destrutcion
CA destruction
在这里没有包含或者被包含的关系,或者是继承与被继承的关系,可是输出结果仍旧是先前我们说的那样。可见,析构函数的处理过程和栈有关系--先进后出。至于在一个类中声明另外类的对象,或者一个类继承另外一个类,都只不过是析构函数的一个特殊应用而已。我想最终还是需要从析构函数的实现原理上去考虑这个问题。呵呵

#15
song42007-03-24 22:41
呵呵
我们说的也是栈的原理啊
但是我说的是原理
为什么呢
因为这样做最安全
怎么样做??
按构造相反的顺序析构
为什么这样最安全呢???
因为在不知道基类里面是否有与子类有关的东西
的情况下,只有与其相反的顺序返回才不会
涉及到析构基类影响子类的情况
#16
幽园香客2007-03-25 11:28
晕,刚才写的那么多东西,提交时出现错误,文字全没了!
谢谢楼上的几位,经过几天来的讨论,我想是不是这样认识这个问题:首先,需要澄清一个错误的认识:调用了子类的析构函数,就释放了占用的所用资源。根据“类与类之间,你做你的,我做我的,以接口作沟通。即使基类与子类也不例外”(引自:钱能 《C++程序设计教程》)的原则,子类的析构函数只是释放自己做申请的那部分资源;基类的资源的释放还是需要调用基类的析构函数。这样以来,基类和子类的的析构函数是各司其职。
总体来说:构造函数是一个入栈过程,而析构函数则是一个出栈的过程。
1