让TList类型安全

yang果5217758

yang果5217758

2016-02-19 17:57

只要你有一台电脑或者手机,都能关注图老师为大家精心推荐的让TList类型安全,手机电脑控们准备好了吗?一起看过来吧!

  在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_castTButton *(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; jnCount; 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_castTWinControl *(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; jCount; 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; jButtonList-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; jlist-Count; j++)
          list-Items[j]-Active = true;
  
      delete list;
  }
  
  通过使用模板技术,我们把问题消灭在了编译时期,而且也提供了自动删除的机制,又由于上面的代码使用了内联技术(inline),所以也没有牺牲代码的效率。
  
  建议你使用STL
  
  通过上面的代码论述,很多初学者可能会害怕,没有类型安全,没有自动删除机制,改代码又那么麻烦,有没有更简单的方法?答案是STL,STL是轻便的,高弹性,高度复用性的,以及类型安全的。假如使用STL,TList的替代品是Vector。
  
展开更多 50%)
分享

猜你喜欢

让TList类型安全

编程语言 网络编程
让TList类型安全

TList

编程语言 网络编程
TList

s8lol主宰符文怎么配

英雄联盟 网络游戏
s8lol主宰符文怎么配

Java语言的接口与类型安全

Java JAVA基础
Java语言的接口与类型安全

安全设置 让你的网站更安全

电脑网络
安全设置 让你的网站更安全

lol偷钱流符文搭配推荐

英雄联盟 网络游戏
lol偷钱流符文搭配推荐

怎么让宝宝吃安全蔬菜

育儿知识
怎么让宝宝吃安全蔬菜

怎么让孩子安全度夏天

电脑网络
怎么让孩子安全度夏天

lolAD刺客新符文搭配推荐

英雄联盟
lolAD刺客新符文搭配推荐

round 方法

round 方法

win10关闭onedrive方法

win10关闭onedrive方法
下拉加载更多内容 ↓