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!");
}
你聪明的,告诉我,我哪里错了?有什么解决方案啊?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库