c++ virtual 虚析构函数 资源释放的讨论
关于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!");
}
你聪明的,告诉我,我哪里错了?有什么解决方案啊?