让TList类型安全

2016-02-19 17:57 0 1 收藏

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

【 tulaoshi.com - 编程语言 】


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

来源:https://www.tulaoshi.com/n/20160219/1616471.html

延伸阅读
如何让宝宝坐车更安全? 坏习惯:为凉爽大开空调 天气越来越热,很多家长一上车就大开空调,想立刻给车内降温,避免热着宝宝。其实,这种做法反而会使宝宝感到不舒服。通常情况下,宝宝的抵抗力远不如大人,尤其在盛夏,身体刚刚适应了外界的气候,到车内后,如突遇冷风,则很容易感冒。 在长途www.Tulaoshi.com驾驶中,有的家长也是一路...
标签: 蔬菜 育儿知识
怎么让宝宝吃安全的蔬菜 蔬菜里面含有多种营养元素,宝宝多吃蔬菜有利于身体发育。但是市场上的蔬菜往往不能满足孩子的成长所需营养,有的蔬菜被打了农药,破坏了营养成分。或者是家长们冲洗不干净、烹饪方式不好等。这些都会影响营养吸收。今天小编就来告诉大家怎样让宝宝吃到营养的蔬菜。 1.冲洗不当 有些从超市里买来的蔬菜,从外观...
标签: 宝宝 教育
如何让孩子学会安全过马路 在去超市的路上,我一边看手机,一边带着乐乐走路。到了马路边要过马路了,乐乐立刻说:“妈妈,你现在是‘盲人’,我带你过马路!”下面是他的指令: “等等。” “走!” 到了马路中间——“停。” 等车过去了,他又说:“好,走!” 我高兴极了,说:“现在乐乐可以过简单的马路了!” “什么是简单的马路?”...
如何让宝宝有安全感 儿童的理想生存环境 一个生命从母体中出来,降临在这个复杂的物质世界里。这个世界蕴涵着复杂的关系,复杂的心态,复杂的意识形态,而婴儿对此一无所知,他仅仅带着巨大的生命动力来到世界。母亲,是他与世界联系的纽带。 在知道母亲不在眼前也依然存在之前,他首先需要时时看到母亲,触摸到她,嗅到她的气...
标签: 电脑入门
对一个电脑用户来说,文字处理工作是必要的,出于保密的原则,我们常常需要对自己的文档进行保护。这里所说的保护有两层含义。第一、确保自己文档中的信息不被其它未授权用户看到;第二、避免宏病毒、误操作及其他各种原因使文档文件受损,保证文档的完整性。现在笔者就以多年文字处理的经验,谈谈在 Word中保护文件的方法和措施。 隐藏文...

经验教程

432

收藏

94
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部