分享
 
 
 

CArray深入体验

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

在编写一个涉及到矢量图形操作系统和空间数据拓扑关系的系统的时候,我利用了CArray来存储空间数据。

在编程过程中,我发现了由于过分信任CArray的功能而引起的一个很不容易察觉的内存泄漏。让我们首先来看看下面的一个类定义:

Class CBreakPoint{

public:

……

double *xy; //the coordinate of these breakpoint,

int nID; //折点的全局唯一标识符

……

}

CBreakPoint::~CBreakPoint()

{

if(xy) delete[]xy;

xy = NULL;

}

CBreakPoint类定义了折线的折点,其中xy表示折点的数组,我们将在具体输入折点的时候对xy进行内存空间申请;nNum表示一条折线中折点的数目;nID表示的折点的全局唯一标识符。再让我们来看一看折线类的定义:

Class CBreakLine{

public:

……

CBreakPoint* m_pBreakPoint;

int nID; //折线的全局唯一标识符

int nVertexID1; //折线的起始端点的nID

int nVertexID2; //折线的结尾端点的nID

int nNum; //该折线中折点的数目

int state; //折线的状态,0表示孤立,1表示连通,在未开始遍历前状态值为0

……

}

类CBreakLine的构造函数和析构函数如下:

CBreakLine::CBreakLine()

{

……

m_pBreakPoint = new CBreakPoint;

}

CBreakLine::~CBreakLine()

{

……

if(m_pBreakPoint) delete m_pBreakPoint;

m_pBreakPoint = NULL;

……

}

CBreakLine类定义了一个基本的空间元素——一条包含若干个折点的折线,在CBreakLine类的构造函数中,我们对m_pBreakPoint进行了内存分配:

CBreakLine::CBreakLine()

{

……

m_pBreakPoint = new CBreakPoint;

……

}

有了折点和折线类的定义,我们可以开始管理我们的折线了。因为在一幅矢量图形中会存在不确定的n条折线,要把这些折线统一的管理起来,我们采用了CArray模板类。首先定义一个集合类CbreakLineSet,在CbreakLineSet类中,定义一个CArray数组成员变量m_breakLises,这个变量代表了空间中所有的折线。类的定义如下:

Class CBreakLineSet{

public:

……

CArray <CBreakLine, CBreakLine&> m_breakLines;

……

}

我们可以利用CArray的成员函数Add(ARG_TYPE newElement)在数组的末尾添加元素(如果必要,扩展数组),利用下标操作符[ ]获取数组项。其他的许多成员函数都使得我们操作m_BreakLines非常的容易,而且,由于CArray由CObject派生,因此本身支持串行化,这样我们可以把折线数据保存到磁盘上。

现在我们来看一下前面定义的三个类,第一层是CBreakPoint,它代表了线上的折点,包括折线的两个端点;第二层是CBreakLine,它代表了折线;第三层是CBreakLineSet,用它来统一管理系统中所有的空间中的折线。最后我们定义集合管理类CSetManage:

Class CSetManage{

public:

……

CbreakLineSet lines, tempLines;

CArray<int, int&> vertexID; //用作堆栈,在遍历过程中存放端点ID

……

public:

……

void Trvel(int nFirstBreakPointID, CbreakLineSet* pLines)

……

}

看起来一切都非常的顺利,现在我们可以利用CArray的成员函数自由的操作m_BreakLines数组。当文件打开时,所有的折线数据都读入到对象

breakLineSet中。

但是,问题出现了。

为了对空间数据进行统计分析,系统要求把所有不连通的折线(即端点和其他折线的端点重合)都删除掉。我们要根据折线的状态判定折线是否连通。因为在任意的时刻,折线的状态并不全部为0,这就要求要有一个临时的空间存放所有的折线并把它们的状态设为0,然后再开始遍历。我采用了如下算法:先把lines中所有的折线复制到tempLines中,删除lines中的所有折线。然后遍历tempLines中的折线,标记连通的折线的状态为1,在遍历结束后,把tempLines中所有状态为1的复制到lines中,然后删除tempLines中的所有数据。

我们采用了如下的语句,请注意:

①for(int i=0; i<lines.m_breakLines.GetSize(); i++)

{

tempLines.m_breakLines.Add(lines.m_breakLines[i]);//

tempLines.m_breakLines[i].state = 0;

}

lines.m_breakLines.RemoveAll(); //清空lines中的所有折线

根据系统的其他因素,我们可以选定tempLines中的一条折线的一个端点作为起始点,然后开始对tempLines中的整个折线网络进行遍历。遍历时采用了递归的思想,具体函数代码如下:

int g_nLines=tempLines.m_breakLines.GetSize(); //系统中折线的数目

void CSetManage::Travel(int nFirstPointID)

{

int nSecondPointID; //折线另一端的折点的nID

for(int i=0; i< g_nLines; i++){

if(tempLines.m_breakLines[i].state == 0

&&( tempLines.m_breakLines[i].nVertexID1 == nFirstPointID

|| tempLInes.m_breakLines[i].nVertexID2 == nFirstPointID))

{

tempLines.m_breakLines[i].state = 1;

nSecondPointID = tempLines.GetOtherVertexID(nFirstPointID);

//获得另一个端点的nID

vertexID.Add(nSecondPointID);

Travel(nSecondPointID);

}

if(i==g_nLines)

{

if(vertexID.GetSize()>0)

{

vertexID.RemoveAt(vertexID.GetSize()-1); //弹出堆栈顶元素

if(vertexID.GetSize()>0) Travel(vertexID[vertexID.GetSize()-1]);

else return;

}

else return;

}

else return;

}

}

②for(int i=0; i<g_nLines; i++)

{

if(tempLines.m_breakLines[i].state == 1)

lines.m_breakLines.Add(tempLines.m_breakLines[i]); //

}

g_nLines = lines.m_breakLines.GetSize();

tempLines.m_breakLines.RemoveAll(); //清空所有的临时分配的空间

到这里,我们开始编译程序,程序运行时一切都很正常,可是在退出程序时出现了错误。跟踪调试,发现问题出在CBreakLines的析构函数中,此时m_pBreakPoint指针所指向的内容已经面目全非。

问题出在那一步呢?肯定是在折线的反复复制、删除过程中。我们知道CArray在添加元素时,会为新的项分配空间,但是它不会为模板使用的类(但前程序中是CBreakLine)类中的指针分配空间,即不会为m_pBreakPoint->xy指针分配新的空间。因此,就是说,当调用

tempLines.m_breakLines.Add(lines.m_breakLines[i]);

时,tempLines中的m_pBreakPoint指向的xy是和lines中m_pBreakPoint指向的xy完全相同的,而在

lines.m_breakLines.RemoveAll(); //清空lines中的所有折线

语句后,函数层层调用各个类的析构函数(CBreakPoint->CBreakLine->CbreakLi

neSet),此时tempLines.m_breakLines[i].m_pBreakPoint指向的内容被清空掉了,但是lines.m_breakLines[i].m_pBreakPoint仍旧存在,其值(指针指向的地址)保留原来的地址(这很危险,指向了不再属于它的空间)。但是程序下面的操作仍旧能够正常执行,因为不涉及到xy的操作。

直到退出程序时,才又一次调用了lines.m_breakLines[i]的析构函数:

if(m_pBreakPoint) delete m_pBreakPoint;

这时,危险爆发了,删除不明的指针导致了内存泄漏。

找到了问题的根源,解决它就很简单了。我们可以重载操作符”=”,取代CArray的Add()函数,在重载操作符时为每一个m_pBreakPoint->xy分配内存空间,使它和原来的指针指向不同的地址,这样当调用RemoveAll时,就不会影响到复制的折线的数据了。重载的函数很简单,如下:

CBreakLine& CBreakLine::operator =(CBreakLine &p)

{

int i=0;

……

if(m_pBreakPoint->xy!=NULL)

{

delete[]m_pBreakPoint->xy;

m_pbreakPoint->xy=NULL;

};

breakPoint->xy = new double[nNum*2];

for(i=0; i<nNum*2; i++)

{

breakPoint->xy[i] = p.breakPoint->xy[i];

}

……

return *this;

}

接下来,添加

再把所有的红色标注的代码分别替换如下:

①tempLines.SetSize(g_nLines);

for(int i=0; i<lines.m_breakLines.GetSize(); i++)

{

tempLines.m_breakLines[i] = lines.m_breakLines[i];

tempLines.m_breakLines[i].state = 0;

}

添加

lines.SetSize(g_nLines);

替换原有代码如下:

int k = 0;

for(int i=0; i<g_nLines; i++)

{

if(tempLines.m_breakLines[i].state == 1)

lines.m_breakLines[k++] = tempLines.m_breakLines[i];②

}

g_nLines = k;

这样,因为我们为所有的新的折线以及折线的折点分配了新的空间,就不会出现内存空间在分配何时方式产生的不明显的冲突。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有