让TList范例安详
当前位置:以往代写 > C/C++ 教程 >让TList范例安详
2019-06-13

让TList范例安详

让TList范例安详

副标题#e#

在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;


#p#副标题#e#

(译注:上面的代码有问题,应该是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;

#p#分页标题#e#

上面的代码可以安详的删除任何从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

#p#副标题#e#

实例代码

//----------------------------------------------------------------------------
// 示例代码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;
}

#p#分页标题#e#

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

发起你利用STL

通过上面的代码阐述,许多初学者大概会畏惧,没有范例安详,没有自动删除机制,改代码又那么贫苦,有没有更简朴的要领?谜底是STL,STL是精练的,高弹性,高度复用性的,以及范例安详的。假如利用STL,TList的替代品是Vector。

    关键字:

在线提交作业