您的位置:首页 > 其它

工作问题积累(二十五)虚析构函数的是怎样的实现原理?_BLOCK_TYPE_IS_VALID

2014-07-31 15:38 363 查看
学了虚函数之后,大家可能会对虚函数半知半解,感觉懂了,似乎又有哪里还不是特别对,这个就是我学了虚函数的感受。

首先书上介绍的虚函数是相同函数名,相同参数,相同返回值的条件下实现的多态,但是为什么析构函数也要设置成虚函数?这个问题决定今天刨下根,查看原理。

由于每个类的析构函数的函数名都不一样,那为什么要设置呢?下面是我们一般经常看到的列子:CBaseChar *pCh = new CChildChar; delete pCh;

class CBaseChar
{
public:
CBaseChar()
{
m_char = new char[256];
cout<<"基类的构造函数:CBaseChar"<<endl;
}
virtual void print()
{
cout<<"CbaseChar::print"<<endl;
}
virtual ~CBaseChar()
{
if (m_char)
{
delete m_char;
m_char = NULL;
}
cout<<"基类的析构函数:CBaseChar"<<endl;
}
private:
char* m_char;
};

class CChildChar:public CBaseChar
{
public:
CChildChar():CBaseChar()
{
m_data = new char[256];
cout<<"派生类的构造函数:CChildChar"<<endl;
}
void print()
{
cout<<"CchildChar::print"<<endl;
}
~CChildChar()
{
if (m_data)
{
delete m_data;
m_data  = NULL;
}
cout<<"派生类的析构函数:CChildChar"<<endl;
}
private:
char* m_data;
};

今天卡在了虚析构函数这点上,我查了很多资料,都只是云云虚析构函数的作用,并没有讲实现这些作用的原理与机制。

我所不明白的就是:为什么,当父类的析构函数声明为虚析构函数时,通过父类的指针去析构子类对象时,就能够一步步从子类一直析构到最底层的父类呢?

以下部分是查阅资料:/article/7612505.html

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

class A
{
public:
~A()
{
cout < <"A::~A" < <endl;
}
};

class B:public A
{
public:
virtual ~B()
{
cout < <"B::~B" < <endl;
}
};

class C:public B
{
public:
~C()
{
cout < <"C::~C" < <endl;
}
};

int main()
{
A *a=new A();
B *b=new B();
C *c=new C();
A *d=new B();
A *e=new C();
B *f=new C();

delete a;
cout < <endl;
delete b;
cout < <endl;
delete c;
cout < <endl;
delete d;
cout < <endl;
delete e;
cout < <endl;
delete f;
cout < <endl;

system("Pause");
}


这段程序运行时有错,当时考的时候题目直接说写出运行结果,我就稀里糊涂得写下来,回来编下发现有错,请教下错在哪,最好告诉我为什么?

出现问题的截图:


分析:原因:1.内存泄漏;所以当程序退出时,系统会收回分配的内存,于是调析构函数,由于内存已被错误地释放,于是就会出现“Debug Assertion Failed”的错误。

2.这个assert说明什么问题呢?说明有一块内存在被释放的时候,它的头部里面的信息已经被改掉了,和预期的不一样。内存分配的程序往往在被分配出的内存块头部放上一些校验信息。这个信息内存的用户是不知道也不应该修改的。这样,在内存被释放的时候,内存分配程序就可以验对这个头部信息是否被改过了。若被改过,就说明发生了内存corruption. 这种corruption有两种可能性:1)有人在内存越界写东西;或者:2)这块内存已经被释放了,又被重复释放了一次。 (在第一次被释放中,是内存分配程序改掉了头部信息)。

3.pHead_>nBlockUse就可能是空指针,或它指向的东西已经不存在了。

玄机逸士回答:

1. 一般来说,如果一个类要被另外一个类继承,而且用其指针指向其子类对象时,如题目中的A* d = new B();(假定A是基类,B是从A继承而来的派生类),那么其(A类)析构函数必须是虚的,否则在delete d时,B类的析构函数将不会被调用,因而会产生内存泄漏和异常;

2. 在构造一个类的对象时,先构造其基类子对象,即调用其基类的构造函数,然后调用本类的构造函数;销毁对象时,先调用本类的析构函数,然后再调用其基类的构造函数;

3. 题目给出的代码是可以编译的,但会出现运行时错误。错误出现在delete d;这一句。为讨论方便,我们不妨将A类和B类改写如下:

class A
{
public:
int a;
~A()
{
cout << "A::~A" << endl;
}
};

class B : public A
{
public:
int b;
virtual ~B()
{
cout << "B::~B" << endl;
}
};

那么A* d = new B();这一句的左边所产生B的对象的内存结构如下:



而A对象的内存结构如下:



可见d只能找到a和A类的析构函数,而无法找到B对象的析构函数,所以当delete d;的时候,B对象所占用的内存就此被泄漏掉了,也从而产生了异常。

如果将A类的析构函数设为虚的,那么A类对象的内存结构将为:



B的内存对象结构如下:



此时通过A* d = new B();,A对象的内存结构中的vfptr,即虚函数表指针,就是B对象的vfptr(B对象的vfptr被bitwise copy,即浅拷贝到A对象的vfptr。如B是从A虚继承而来的,则需要加一个offset,情况要更复杂,见

http://blog.csdn.net/pathuang68/archive/2009/04/24/4105902.aspx),因此,A对象的vfptr所指向的是B对象的虚函数表,而B的析构函数位于书函数表0的位置,因此,这样就可以通过A类对象的指针d,找到B类对象的析构函数,从而在delete
d;时,可以销毁B对象,而不会产生内存泄漏和异常。

事实上,该题目只要将A中的析构函数设成虚的,B类中的析构函数前面的virtual关键字不管是否存在,其析构函数也一定是虚的,C类同此道理。因此,得到结论就是,只要能够保证继承关系中最高的基类的析构函数是虚的,那么就不会产生前面所谈及的问题。这就是为什么在想使用多态特性的时候,需要将基类的析构函数设成虚的真正原因。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: