关于c++ virtual,析构函数的讨论已经挺多了,参见
http://zxjgoodboy.blog.sohu.com/61482463.html
http://blog.csdn.net/zoukh/article/details/16624
http://blog.csdn.net/han_348154920/article/details/5944351
http://hi.baidu.com/bystander1983/blog/item/d61627553e3afe52d10906d2.html
http://www.cnblogs.com/powersun/archive/2007/08/13/853015.html
http://www.cnblogs.com/xd502djj/archive/2010/09/22/1832912.html
但是这些文章大多在讨论:通过虚析构函数,可以利用多态顺利释放资源,防止内存泄露。
但是我在使用时却恰恰发生了相反的事情,不是内存泄漏,而是内存重复删除。这也是一个需要深思的问题。
我们还是以传统的animal和dog为例。
首先是基类 animal
#pragma once
class animal
{
public :
animal( void );
virtual ~ animal( void );
int * type;
virtual void allocate( int n);
virtual void release();
};
实现
#include " animal.h "
#include " stdio.h "
animal::animal( void ):type( 0 )
{
}
animal:: ~ animal( void )
{
release();
}
void animal::allocate( int n )
{
release();
puts( " allocate from Animal start! " );
type = new int [n];
puts( " allocate from Animal end! " );
}
void animal::release()
{
puts( " release from Animal start! " );
if (type)
{
delete [] type;
}
puts( " release from Animal end! " );
}
.
派生类dog
#pragma once
#include " animal.h "
class dog :
public animal
{
public :
dog( void );
virtual ~ dog( void );
int * country;
virtual void allocate( int n);
virtual void release();
};
.
实现
#include " dog.h "
#include " stdio.h "
dog::dog( void ):country( 0 )
{}
dog:: ~ dog( void )
{}
void dog::allocate( int n)
{
puts( " allocate from Dog start! " );
animal::allocate(n);
country = new int [n];
puts( " allocate from Dog end! " );
}
void dog::release()
{
puts( " release from Dog start! " );
animal::release();
if (country)
{
delete []country;
}
puts( " release from Dog end! " );
}
.
.
main里考察分别使用基类和派生类指针的情形
#include " dog.h "
#include " stdio.h "
int main()
{
animal * a = new dog();
a -> allocate( 12 );
delete a;
puts( " \na finished!\n " );
dog * b = new dog();
b -> allocate( 12 );
delete b;
puts( " \nb finished!\n " );
}
运行结果是
allocate from Dog start !
release from Dog start !
release from Animal start !
release from Animal end !
release from Dog end !
allocate from Animal start !
allocate from Animal end !
allocate from Dog end !
release from Animal start !
release from Animal end !
a finished !
allocate from Dog start !
release from Dog start !
release from Animal start !
release from Animal end !
release from Dog end !
allocate from Animal start !
allocate from Animal end !
allocate from Dog end !
release from Animal start !
release from Animal end !
b finished !
.
两种情形是一致的,但是要注意对于单一情形,在allocate和析构函数中,都是在基类里调用了release(),但表现的结果确完全不同:
allocate成功使用了多态,析构函数却只是调用了animal::release()
产生这种问题的根源在于:派生类的普通成员函数会对基类的同签名函数产生隐藏,调用派生类的函数不会触发基类的函数;但是析构函数就恰恰相反,调用派生类的析构函数后会紧接着调用基类的析构函数。
也就是说,我在dog::allocate()中显式的调用animal::allocate(),也就顺次显式调用release(),实际上使用派生类指针调用基类函数,恰好满足多态条件,因此运行正常。
但是在dog::~dog()中,我没用显示调用基类析构函数,而是系统隐式调用的;但是系统隐式调用时,派生类已经析构了,它是按照animal::~animal()来调用的,那么release()也就只是有基类指针使用,因此没有使用多态,animal发生了内存泄露。
那么,我如果在dog::~dog()中显式的调用release()行不行呢?
我们试试看
dog:: ~ dog( void )
{
release();
}
现在的程序输出是
allocate from Dog start !
release from Dog start !
release from Animal start !
release from Animal end !
release from Dog end !
allocate from Animal start !
allocate from Animal end !
allocate from Dog end !
release from Dog start !
release from Animal start !
release from Animal end !
release from Dog end !
release from Animal start !
没错,就是这些!程序就在这卡着。(我的开发环境是 vs2010+win7)
尝试调试,说程序陷入死锁状态。仔细观察最后5句,发现 dog::~dog()里的release()顺利执行,在调用基类的析构函数时卡住了。类似的情况我在另一个大点的项目中则是直接崩溃了。
分析一下这里原因也好理解,animal::release()函数被调用了两次,第二次调用时产生死锁或者崩溃。
那么是我程序哪里实现的有问题么?
allocate与release都应该是virtual的吧?
dog::release()中必须要调用 animal::release()才合理吧?
那么现在陷入了两难困境:dog::~dog()中到底应不应该调用release()呢?调用,程序崩溃;不调用,内存泄露。
.
.
很让人头疼的问题。
无奈,我只好曲线救国,把release拆分成了两部分来实现,总算可以实现目标,但这样确实太丑了。
animal.h
#pragma once class animal { public: animal(void); virtual ~animal(void); int * type; virtual void allocate(int n); virtual void release(); void releaseMyData(); };
animal.cpp
#include " animal.h "
#include " stdio.h "
animal::animal( void ):type( 0 )
{
}
animal:: ~ animal( void )
{
release();
}
void animal::allocate( int n )
{
release();
puts( " allocate from Animal start! " );
type = new int [n];
puts( " allocate from Animal end! " );
}
void animal::release()
{
releaseMyData();
}
void animal::releaseMyData()
{
puts( " release from Animal start! " );
if (type)
{
delete [] type;
}
puts( " release from Animal end! " );
}
dog.h
#pragma once
#include " animal.h "
class dog :
public animal
{
public :
dog( void );
virtual ~ dog( void );
int * country;
virtual void allocate( int n);
virtual void release();
void releaseMyData();
};
dog.cpp
#include " dog.h "
#include " stdio.h "
dog::dog( void ):country( 0 )
{}
dog:: ~ dog( void )
{
releaseMyData();
}
void dog::allocate( int n)
{
puts( " allocate from Dog start! " );
animal::allocate(n);
country = new int [n];
puts( " allocate from Dog end! " );
}
void dog::release()
{
animal::release();
releaseMyData();
}
void dog::releaseMyData()
{
puts( " release from Dog start! " );
if (country)
{
delete []country;
}
puts( " release from Dog end! " );
}
你聪明的,告诉我,我哪里错了?有什么解决方案啊?