在C++ Builder中,VCL库包含一个TList类,用于管理指针列表。然而,TList类含有许多缺点,其中最严重的缺点是TList缺乏类型的安全性及内存空间的自动释放。本文将探讨TList类的缺点,并提出改进方法。
一、TList的问题
1、TList主要用来存储对象的指针,使用方便,但是这个类的主要缺点是缺乏安全性,因为它存储并维护的是空指针(void *),让我们看看它的Add方法的原型:
int __fastcall Add(void * Item);
C++编译器能正确地将任何类型的指针转换为void *型指针,因此,TList的Add方法能接受你传给它的任何一个指针。这其中就产出了一个问题,当你想维护TList中的对象指针列表时,你能肯定TList中的指针具有相同类型吗?例如:
TList *ButtonList = new TList; // create a list of buttons
ButtonList->Add(Button1); // add some buttons to the list
ButtonList->Add(Button2); //
ButtonList->Add( new TButton(this)); // OK so far
ButtonList->Add(Application); // Application is not a button
ButtonList->Add(Form1); // neither is Form1
ButtonList->Add((void *)534);
ButtonList->Add(Screen);
以上代码能正确编译通过,从中我们能看出,TList能处理任何对象,它在接受任何一个对象时不做任何类型检查。当你要引用TList中的一个对象时,由于它是一个空指针,你必须将它转换为一个正确类型的指针,具体方法如下:
TList *ButtonList = new TList;
ButtonList->Add(Button1);
… … // 生成一个对象指针列表
TButton *button = reinterpret_cast<TButton *>(ButtonList->Items[1]);
//引用对象
button->Caption = "I hope it's really a button";
delete ButtonList;
在以上代码中,TList的Item数组返回一个空指针(void *),要引用其中的一个TButton型的指针,必须将其转换为TButton型,这种转换的前提条件是你能肯定TList的Item返回的指针原来指向一个TButton对象。也许有个认为用dynamic_cast 操作符来完成这种转换,然而不幸的是由于空指针不含有任何类型信息,上述缺点仍然无法消除。
2、TList的另一个缺点是它不能自动删除其列表中的指针,要完全删除列表,必须遍历整个列表,将列表中的指针一个个删除。例如:
TList *ButtonList = new TList; // 生成一个对象列表
ButtonList->Add(new TButton(Handle)); // 向列表中添加TButton对象
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;
从表面上看,上述代码非常完美,但是深入一点,我们会发现一个缺点:删除列表中的某个指针时,该指针是一个空指针,而删除一个空指针与删除一个TButton型的指针是不同的,因为删除一个空指针不能自动执行一个对象的析构函数,也就不能执行析构函数中的内存释放与重新分配操作,从而会造成内存空洞。
为了能正确删除TList对象中的指针,必须将它们的类型进行转换,这样编译器才能正确调用类的析构函数。因为VCL中的所有析构函数都是虚函数,我们可以将列表中的所有转换为一个公共的基类,例如:
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=0; j<nCount; j++)
delete reinterpret_cast<TWinControl *>(ControlList->Items[j]);
delete ControlList;
若TList对象中的指针对象都是从TWinControl继承下来的,则上述代码工作正确,若某指针对象是从TDataSet继承下来的,则上述代码工作又有问题。
二、改进方法
如果TList知道它所处理的对象,那么它的许多缺点就自动消除。基于此,在TList的基础上我们引进了一个新类TTypedList,它是从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)
{
}
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);
if(bAutoDelete && (nIndex != -1))
delete Item;
return nIndex;
}
__property T* Items[int Index] = {read=Get, write=Put};
};
#endif
最后根据改进的TTypedList类举个例了:
在C++ Builder4中建一个新的应用程序,主要代码如下:
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#include "typedlist.h"
//---------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------
void __fastcall TForm1::CreateButtons()
{
// Create a list of buttons.
TTypedList <TButton> *ButtonList = new TTypedList <TButton>(false);
ButtonList->Add(new TButton(this));
ButtonList->Add(new TButton(this));
ButtonList->Add(new TButton(this));
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;
}
//-------------------------------------------------------------------------
void __fastcall TForm1::FormShow(TObject *Sender)
{
CreateButtons();
}
//-------------------------------------------------------------------------以上代码在C++ Builder5+Pwin2000 Professional环境下中通过。