关于析构的问题,..附代码...

Torrice 2006-08-03 11:15:50
1.正常的程序*****************************

#include <iostream>

using namespace std;

class CSomething
{
public:
CSomething(){cout<<"2";}
virtual ~CSomething(){cout<<"2";}
};

class CParent
{
public:
CParent(){cout<<"1";}
virtual ~CParent(){cout<<"1";}
};

class CChild : public CParent
{
public:
CChild(){cout<<"3";}
virtual ~CChild(){cout<<"3";}

protected:
CSomething something;
};

int main(int argc,char** argv)
{
CParent* pParent = new CChild();
delete pParent;
return 0;
}

输出"123321"

2.**************************************

#include <iostream>

using namespace std;

class CSomething
{
public:
CSomething(){cout<<"2";}
~CSomething(){cout<<"2";}
};

class CParent
{
public:
CParent(){cout<<"1";}
~CParent(){cout<<"1";}
};

class CChild : public CParent
{
public:
CChild(){cout<<"3";}
~CChild(){cout<<"3";}

protected:
CSomething something;
};

int main(int argc,char** argv)
{
CParent* pParent = new CChild();
delete pParent;
return 0;
}

为何输出"1231"?

3.****************************************

#include <iostream>

using namespace std;

class CSomething
{
public:
CSomething(){cout<<"2";}
~CSomething(){cout<<"2";}
};

class CParent
{
public:
CParent(){cout<<"1";}
virtual ~CParent(){cout<<"1";}
};

class CChild : public CParent
{
public:
CChild(){cout<<"3";}
~CChild(){cout<<"3";}

protected:
CSomething something;
};

int main(int argc,char** argv)
{
CParent* pParent = new CChild();
delete pParent;
return 0;
}

为何也运行正常,输出"123321"?

4.**********************************

#include <iostream>

using namespace std;

class CSomething
{
public:
CSomething(){cout<<"2";}
~CSomething(){cout<<"2";}
};

class CParent
{
public:
CParent(){cout<<"1";}
~CParent(){cout<<"1";}
};

class CChild : public CParent
{
public:
CChild(){cout<<"3";}
virtual ~CChild(){cout<<"3";}

protected:
CSomething something;
};

int main(int argc,char** argv)
{
CParent* pParent = new CChild();
delete pParent;
return 0;
}

为何不能正常运行? 中间结果为"1231"?

5.继承中的析够内在机制是什么?讨论讨论吧.

讲的有理就有分:)
...全文
617 38 打赏 收藏 转发到动态 举报
写回复
用AI写文章
38 条回复
切换为时间正序
请发表友善的回复…
发表回复
kulue 2006-08-07
  • 打赏
  • 举报
回复
看书上虚函数的介绍你就知道了
猫腻儿姐姐 2006-08-07
  • 打赏
  • 举报
回复
在这里首先你用了一个基类的指针CParent* pParent = new CChild()指向一个派生类的对象(这便是多态,用到多态,那么消息是动态绑定的,只有运行到那里才能知道调用哪个成员),如果基类的析构函数是非虚的,那么delete时,派生类的对象将不能得到释放,造成内存泄露。
若基类是虚析构函数,那么当应用程序退出时,系统会自动从当前的派生类析构函数到基类依次调用,释放空间!
希望你会明白!
jiojralf 2006-08-06
  • 打赏
  • 举报
回复

你的意思是说
CChild在构造的时候发现有CSomething对象,于是构造CSomething,于是呢,CSomething构造比CChild晚开始但早结束,是不是这样?
fangrk 2006-08-06
  • 打赏
  • 举报
回复
第四个例子我在VC.Net2003命令行模式下也运行成功的。

我倒是觉得没必要钻到汇编层次去了解虚函数的什么vtable,vptr
fangrk 2006-08-06
  • 打赏
  • 举报
回复
恩,写错了,应该是析构子类


我上面说的“调用自己的构造函数”不是很妥当,应该是“调用自己的构造函数中{}的部分”

看过Effective C++或Inside C++ Model的都知道这么一个例子:
class SomeClass
{
public:
SomeClass(const char* Name);
...
private:
std::string name;
....
}

假设Name不是空,对于构造函数,一种写法是:
SomeClass(const char* Name):name(Name){}
第二种是:
SomeClass(const char* Name){name=Name;}

两种都是正确的。
现在大家都知道,第一种写法是高效的,因为name只有一次函数调用:string::string(const char*);
第二种写法是两次函数开销:string::string(); string& string::operator=(const char*);
从这个例子也可以倒推,在构造函数的{}代码执行前,类成员变量/常量已经初始化过了。
fmddlmyy 2006-08-06
  • 打赏
  • 举报
回复
前面回复有个笔误:
pParent等于pChild加4。

调试Torrice的问题时,我顺便看了看为什么delete的地址不是分配地址就会出现assert。可以说一说:

CRT在分配空间时,会在返回地址前多分配20个字节,这是一个叫作_CrtMemBlockHeader的结构。CRT通过这个结构的内容管理动态内存分配(常用手法)。
如果delete的地址B不是分配地址A,CRT在将地址B前面的20个字节解释成_CrtMemBlockHeader结构时,就会读到错误的数据,产生assert。
fmddlmyy 2006-08-06
  • 打赏
  • 举报
回复
关于析构的问题

第4个例子的写法显然是不对的。但要解释清楚为什么出现assert,可要费些力气。

如果对象有虚函数,它的前4个字节是vptr,否则没有vptr。
在例4中,基类没有虚函数(没有vptr),派生类有虚函数(有vptr)。在执行
CParent* pParent = new CChild();
时,编译器会先new一个CChild对象,假设其地址是0x00431cb0。因为CParent没有vptr,编译器在给pParent赋值时,会让其指向vptr后的基类非静态成员。即pParent的值是0x00431cb4。

如果代码写成:
CChild *pChild = new CChild();
CParent *pParent = pChild;

就很容易看出来pParent等于pParent加4。

语句
delete pParent;
显然调用的是基类的析构函数。因为delete的地址0x00431cb4不是分配的地址0x00431cb0,所以出现assert。

所以除非有特殊需要,不要在派生类中将基类的非虚函数改成虚函数。
析构的机制,前面的朋友都讲过了,也没什么好说的。如何通过vptr实现多态,应该是耳熟能详的东西吧。

可以说一说的就是,析构函数是编译器合成的特殊函数,编译器会在派生类的析构函数中加入了成员析构和基类析构的代码。

26: virtual ~CChild(){printf("析构 CChild\n");}
0040EA60 push ebp
0040EA61 mov ebp,esp
0040EA63 push 0FFh
0040EA65 push offset __ehhandler$??1CChild1@@UAE@XZ (004126c9)
0040EA6A mov eax,fs:[00000000]
0040EA70 push eax
0040EA71 mov dword ptr fs:[0],esp
0040EA78 sub esp,44h
0040EA7B push ebx
0040EA7C push esi
0040EA7D push edi
0040EA7E push ecx
0040EA7F lea edi,[ebp-50h]
0040EA82 mov ecx,11h
0040EA87 mov eax,0CCCCCCCCh
0040EA8C rep stos dword ptr [edi]
0040EA8E pop ecx
0040EA8F mov dword ptr [ebp-10h],ecx
0040EA92 mov eax,dword ptr [ebp-10h]
0040EA95 mov dword ptr [eax],offset CChild1::`vftable' (0042416c)
0040EA9B mov dword ptr [ebp-4],0
0040EAA2 push offset string "\xce\xf6\xb9\xb9 CChild1\n" (00425490)
0040EAA7 call printf (00402c90)
0040EAAC add esp,4
0040EAAF mov ecx,dword ptr [ebp-10h]
0040EAB2 add ecx,4
0040EAB5 call @ILT+285(CSomething1::~CSomething1) (00401122)
0040EABA mov dword ptr [ebp-4],0FFFFFFFFh
0040EAC1 mov ecx,dword ptr [ebp-10h]
0040EAC4 call @ILT+100(CParent1::~CParent1) (00401069)
0040EAC9 mov ecx,dword ptr [ebp-0Ch]
0040EACC mov dword ptr fs:[0],ecx
0040EAD3 pop edi
0040EAD4 pop esi
0040EAD5 pop ebx
0040EAD6 add esp,50h
0040EAD9 cmp ebp,esp
0040EADB call __chkesp (00402d10)
0040EAE0 mov esp,ebp
0040EAE2 pop ebp
0040EAE3 ret

从上面的汇编代码中可以看到:
开始是用户写在~CChild()里的代码,然后编译器加入了对成员对象析构函数的调用(0040EAB5),对基类析构函数的调用(0040EAC4)。

千万不要将析构函数与一般虚函数混淆起来。
对于一般的虚函数,编译器不会做什么手脚,vptr中是什么函数就调什么函数,绝对不会调到基类的函数。
别的就没什么了。
fmddlmyy 2006-08-06
  • 打赏
  • 举报
回复
其实Torrice(酷爱C++)的问题中最难解释的是第4个例子为什么在VC6环境不能正常运行。
Torrice可能是因此才想进一步了解内在机制。

而大家总在这里讲一些C++的基础知识,难怪Torrice会失望。:)


langzi8818 2006-08-05
  • 打赏
  • 举报
回复
是楼主没有仔细看大家的恢复,没有去动脑筋想或根本还没有明白虚函数的机制
jiojralf 2006-08-05
  • 打赏
  • 举报
回复
而且我看的c++入门也没说基类一定要有虚拟的析构函数啊?
难道书本有误。。。
jiojralf 2006-08-05
  • 打赏
  • 举报
回复
为什么Csomething的构造要先于Cchild的构造呢?
Torrice 2006-08-05
  • 打赏
  • 举报
回复
楼上荒唐,我时刻关注每个人的恢复,并仔细阅读;

至于你说得"没有去动脑筋想或根本还没有明白虚函数的机制"这点更是有问题,我提出这个问题就是为了解决你说的这句话.再说也不见得我就一点也不懂...

如果说得不当,多包涵
iseealv 2006-08-05
  • 打赏
  • 举报
回复
为什么Csomething的构造要先于Cchild的构造呢?
CChild在构造函数运行完的时候表示对象建立起来,既然对象建立起来了,说明something对象应该已经先于CChild的对象建立
iseealv 2006-08-05
  • 打赏
  • 举报
回复
1.
CParent* pParent = new CChild();
由于CChild继承CParent,所以CChild构造前会先构造CParent
输出:1
然后构造成员CSomething something;
输出:2
然后调用CChild自己的构造函数
输出:3
delete pParent;

pParent所指的静态对象类型(等号左边的对象类型)是CParent,基类CParent的析构函数是虚拟的
基类存在虚拟函数,便决定了pParent可能有动态类型对象(实际所指对象)
pParent所指的动态对象类型是CChild,且虚拟函数是析构函数,故调用自己的析构函数前先调用
~~~~~~~
基类的析构
~~~~~~是不是笔误啊。还是先析构子类啊
输出:3
析构成员something
输出:2
最后调用本身的析购函数:1
总的输出是:123321
fangrk 2006-08-05
  • 打赏
  • 举报
回复
1.
CParent* pParent = new CChild();
由于CChild继承CParent,所以CChild构造前会先构造CParent
输出:1
然后构造成员CSomething something;
输出:2
然后调用CChild自己的构造函数
输出:3
delete pParent;

pParent所指的静态对象类型(等号左边的对象类型)是CParent,基类CParent的析构函数是虚拟的
基类存在虚拟函数,便决定了pParent可能有动态类型对象(实际所指对象)
pParent所指的动态对象类型是CChild,且虚拟函数是析构函数,故调用自己的析构函数前先调用基类的析构
输出:3
析构成员something
输出:2
最后调用本身的析购函数:1
总的输出是:123321


2.
很干净,都没有virtual
CParent* pParent = new CChild();
先调用基类的构造,然后构造成员something,最后调用自己的构造
输出:123
delete pParent;
由于不存在虚拟析购,所以pParent不存在动态对象类型
delete pParent;仅仅析购的是静态对象部分CParent
不会调用基类的析构,所以资源泄露了
输出:1
总的输出:1231

3.
CParent* pParent = new CChild();
先构造基类CParent,然后构造something,最后调用自己的构造函数
输出123
由于CChild的基类CParent的析构是虚拟的,故继承类的析购也是虚拟的
所以输出结果如同演示1


4.
CParent* pParent = new CChild();
先构造基类CParent,然后构造something,最后调用自己的构造函数
输出123
delete pParent;
pParent静态对象类型是CParent,CParent没有虚拟函数,故pParent无动态类型
delete pParent;只是析构CParent
故该部分的输出结果如同演示2:1
总的输出结果是:1231
triace_zhang 2006-08-04
  • 打赏
  • 举报
回复
<<Inside the C++ Object Model>>
对于各种情况下的虚拟机制的实现有很详细的解释,这不是几句话就能说清楚的,楼主
对这个感兴趣的话应该看这本书。
庄鱼 2006-08-04
  • 打赏
  • 举报
回复
基类的虚析构函数的意思是:此处并不一定是真正的对象解构点,具体情况得依赖对象本身类型来决定。因此,当出现继承的派生对象时,其实际调用的是派生对象的析构函数,而非自己的析构函数。然而,如果不是虚析构函数,在基类解构时将直接调用基类的析构函数,这样虽然对象是其派生对象,但事实上仅有基类对象部分能够很好的析构,而派生部分并不能析构(派生对象没有析构释放资源)。
这样,1、3正确也就很正常了,2、4不正确是因为派生对象并没有充分析构。
langzi8818 2006-08-04
  • 打赏
  • 举报
回复
DELETE 的是父类指针,因为父类没有虚拟的析构函数,所以调用的是父类的析构函数,而不是子类的。
sinall 2006-08-04
  • 打赏
  • 举报
回复
大家都总结,我也凑热闹:
“要把此类作为基类,一定要用虚析构函数”这个结论似乎不如“如果该类有一个虚函数,那么它一定也应该有一个虚析构函数”更具体。
理由如下:有时一个类是不知道自己是否会被继承,例如,我们想要子类化一个已有类。
kingsun555 2006-08-04
  • 打赏
  • 举报
回复
最后总结的好
加载更多回复(18)

64,686

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧