王朝网络
分享
 
 
 

让TList类型安全

王朝other·作者佚名  2008-06-01
宽屏版  字体: |||超大  

在VCL中包含有一个TList类,相信很多朋友都使用过,它可以方便的维护对象指针,所以很多朋友都喜欢用它

来实现控件数组。不幸的是,这个TList类有一些问题,其中最重要就是缺乏类型安全的支持。

这篇文章介绍如何从TList派生一个新类来实现类型安全,并且能自动删除对象指针的方法。

TList的问题所在

对于TList的方便性这里就不多说,我们来看一下,它到底存在什么问题,在Classes.hpp文件中,我们可以看到函数的原型是这样申明的:

int __fastcall Add(void * Item);

编译器可以把任何类型的指针转换为void*类型,这样add函数就可以接收任何类型的对象指针,这样问题就来了,假如你仅维护一种类型的指针也许还看不到问题的潜在性,下面我们以一个例子来说明它的问题所在。假设你想维护一个TButton指针,TList当然可以完成这样的工作但是他不会做任何类型检查确保你用add函数添加的一定是TButton*指针。

TList *ButtonList = new TList; // 创建一个button list

ButtonList->Add(Button1); // 添加对象指针

ButtonList->Add(Button2); //

ButtonList->Add( new TButton(this)); // OK so far

ButtonList->Add(Application); // Application不是button

ButtonList->Add(Form1); // Form1也不是

ButtonList->Add((void *)534);

ButtonList->Add(Screen);

上面的代码可以通过编译运行,因为TList可以接收任何类型的指针。

当你试图引用指针的时候,真正的问题就来了:

TList *ButtonList = new TList;

ButtonList->Add(Button1);

ButtonList->Add(Button2);

ButtonList->Add(Application);

TButton *button = reinterpret_cast<TButton *>(ButtonList->Items[2]);

button->Caption = "I hope it's really a button";

delete ButtonList;

相信你已经看到了问题的所在,当你需要取得指针引用的时候TList并不知道那是个什么类型的指针,所以你需要转换,但谁能保证ButtonList里一定是Button指针呢?你也许会马上想到使用dynamic_cast来进行转化。

不幸再次降临,dynamic_cast无法完成这样的工作,因为void类型的指针不包含任何类型信息,这意味着你不能使用这样的方法,编译器也不答应你这样做。

dynamic_cast不能使用了,我们唯一的方法就是使用reinterpret_cast,不过这个操作符同以前c的强制类型转换没有任何区别,它总是不会失败,你可以把任何在任何指针间转换。这样你就没有办法知道List中是否真的是我们需要的Button指针。在上面的代码片断中,问题还不是非常严重,我们试图转换的指针是Application,当我们改变Caption属性的时候,最多把标题栏的Caption属性改了,可是假如我们试图转换的对象没有相应的属性呢?

TList的第二个问题是,它自动删除对象指针的功能,当我们析构TList的时候,它并不能自动释放维护的指针数组的对象,很多时候我们需要用手工的方法来完成这样一件事情,下面的代码片断显示了如何释放他们:

TList *ButtonList = new TList; // create a list of buttons

ButtonList->Add(new TButton(Handle)); // add some buttons to the list

ButtonList->Add(new TButton(Handle));

ButtonList->Add(new TButton(Handle));

ButtonList->Add(new TButton(Handle));

...

...

int nCount = ButtonList->Count;

for (int j=0; j<nCount; j++)

delete ButtonList->Items[j];

delete ButtonList;

(译注:上面的代码有问题,应该是for(int j=nCount-1;j>=0;j--),及要反过来循环,否则可能出现AV)

表面上看来,上面的代码能很好的工作,但是假如你深入思考就会发现潜在的问题。Items[j]返回的是一个void指针,这样delete语句将会删除void指针,但是删除void指针与删除TButton指针有很大的不同,删除void指针并不会调用对象的析构器,这样存在于析构器中的释放内存的语句就没有机会执行,这样将造成内出泄漏。

完了能完全的删除对象指针,你必须让编译器知道是什么类,才能调用相应的析构器。好在VCL的析构器都是虚拟的,你可以通过转换为基类来安全的删除派生类。比如假如你的List里包含了Button和ComboBox,有可以把他们转换为TComponent、TControl、TWinControl来安全的删除他们,示例代码如下:

TList *ControlList = new TList;

ControlList->Add(new TButton(Handle));

ControlList->Add(new TEdit(Handle));

ControlList->Add(new TComboBox(Handle));

int nCount = ControlList->Count;

for (int j=nCount; j>=0; j--)

delete reinterpret_cast<TWinControl *>(ControlList->Items[j]);

delete ControlList;

上面的代码可以安全的删除任何从TwinControl派生的子类,但是假如是TDatset呢?TDataSet并不是从TWinControl继续的,这样delete将调用TWinControl的析构器,这同样可能造成运行时的错误。

改进TList

通过上面的论述,我们已经大概了解了TList需要如何改进。假如TList知道它处理的对象的类型,大多数的问题就解决了。下面的代码就是为了这个目标来写的:

#ifndef TTYPEDLIST_H

#define TTYPEDLIST_H

#include <classes.hpp>

template <class T>

class TTypedList : public TList

{

private:

bool bAutoDelete;

protected:

T* __fastcall Get(int Index)

{

return (T*) TList::Get(Index);

}

void __fastcall Put(int Index, T* Item)

{

TList::Put(Index,Item);

}

public:

__fastcall TTypedList(bool bFreeObjects = false)

:TList(),

bAutoDelete(bFreeObjects)

{

}

// 注重:没有析构器,直接调用Delete来释放内存

// 而且Clean时虚拟的,你知道怎么做了?

int __fastcall Add(T* Item)

{

return TList::Add(Item);

}

void __fastcall Delete(int Index)

{

if(bAutoDelete)

delete Get(Index);

TList::Delete(Index);

}

void __fastcall Clear(void)

{

if(bAutoDelete)

{

for (int j=0; j<Count; j++)

delete Items[j]; //(译注:这行代码同样存在上面提到的问题)

}

TList::Clear();

}

T* __fastcall First(void)

{

return (T*)TList::First();

}

int __fastcall IndexOf(T* Item)

{

return TList::IndexOf(Item);

}

void __fastcall Insert(int Index, T* Item)

{

TList::Insert(Index,Item);

}

T* __fastcall Last(void)

{

return (T*) TList::Last();

}

int __fastcall Remove(T* Item)

{

int nIndex = TList::Remove(Item);

// 假如bAutoDelete is true,我们将自动删除item

if(bAutoDelete && (nIndex != -1))

delete Item;

return nIndex;

}

__property T* Items[int Index] = {read=Get, write=Put};

};

#endif

实例代码

//----------------------------------------------------------------------------

// 示例代码1

#incude "typedlist.h"

void __fastcall TForm1::CreateButtons()

{

// false,不自动删除

TTypedList <TButton> *ButtonList = new TTypedList <TButton>(false);

ButtonList->Add(new TButton(this));

ButtonList->Add(new TButton(this));

ButtonList->Add(new TButton(this));

// ButtonList->Add(Application); <<-- 无法通过编译

for (int j=0; j<ButtonList->Count; j++)

{

ButtonList->Items[j]->Caption = "Button" + IntToStr(j);

ButtonList->Items[j]->Left = 250;

ButtonList->Items[j]->Top = 50 + j*25;

ButtonList->Items[j]->Parent = this;

}

delete ButtonList;

}

//----------------------------------------------------------------------------

// 实例代码2

#incude "typedlist.h"

void __fastcall TForm1::CreateButtons()

{

typedef TTypedList <TButton> TButtonList;

TButtonList *ButtonList = new TButtonList(true);

ButtonList->Add(new TButton(this));

...

delete ButtonList;

}

//----------------------------------------------------------------------------

// Code Example 3: A list of tables and queries

#incude "typedlist.h"

void __fastcall TForm1::OpenDataSets()

{

typedef TTypedList <TDataSet> TDataSetList;

TDataSetList *list = new TDataSetList(false);

list->Add(Table1);

list->Add(Table2);

list->Add(Table3);

list->Add(Query1);

for (int j=0; j<list->Count; j++)

list->Items[j]->Active = true;

delete list;

}

通过使用模板技术,我们把问题消灭在了编译时期,而且也提供了自动删除的机制,又由于上面的代码使用了内联技术(inline),所以也没有牺牲代码的效率。

建议你使用STL

通过上面的代码论述,很多初学者可能会害怕,没有类型安全,没有自动删除机制,改代码又那么麻烦,有没有更简单的方法?答案是STL,STL是轻便的,高弹性,高度复用性的,以及类型安全的。假如使用STL,TList的替代品是Vector。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
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- 王朝网络 版权所有