程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 讓TList類型安全

讓TList類型安全

編輯:關於C++

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved