讓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_cast
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
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
delete ControlList;
上面的程式碼可以安全的刪除任何從TwinControl派生的子類,但是如果是TDatset呢?TDataSet並不是從TWinControl繼承的,這樣delete將呼叫TWinControl的析構器,這同樣可能造成執行時的錯誤。
改進TList
透過上面的論述,我們已經大概瞭解了TList需要如何改進。如果TList知道它處理的物件的型別,大多數的問題就解決了。下面的程式碼就是為了這個目標來寫的:
#ifndef TTYPEDLIST_H
#define TTYPEDLIST_H
#include
template
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 bFrees = 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
}
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
ButtonList->Add(new TButton(this));
ButtonList->Add(new TButton(this));
ButtonList->Add(new TButton(this));
// ButtonList->Add(Application); <
for (int j=0; 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
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
TDataSetList *list = new TDataSetList(false);
list->Add(Table1);
list->Add(Table2);
list->Add(Table3);
list->Add(Query1);
for (int j=0; j
list->Items[j]->Active = true;
delete list;
}
透過使用模板技術,我們把問題消滅在了編譯時期,而且也提供了自動刪除的機制,又由於上面的程式碼使用了內聯技術(inline),所以也沒有犧牲程式碼的。
建議你使用STL
透過上面的程式碼論述,很多初學者可能會害怕,沒有型別安全,沒有自動刪除機制,改程式碼又那麼麻煩,有沒有更簡單的方法?答案是STL,STL是輕便的,高彈性,高度複用性的,以及型別安全的。如果使用STL,TList的替代品是Vector。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-956432/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- TypeScript 型別安全TypeScript型別
- 字串、數值 等型別的相互 安全轉換字串型別
- 解析Java語言的介面與型別安全(轉)Java型別
- Golang:cast安全且易用的型別轉換工具GolangAST型別
- 資料型別,型別轉換資料型別
- PHP 型別轉換&&型別強制轉換PHP型別
- java型別轉換與強制型別轉換(轉)Java型別
- java- 型別-轉換:基本型別以及包裝型別的轉換Java型別
- 型別轉換型別
- Java資料型別及型別轉換Java資料型別
- 字元型別轉換成時間型別字元型別
- Oracle Long型別轉換為Clob型別Oracle型別
- C#變數型別(1):引用型別和值型別 (轉)變數型別
- 淺談PHP弱型別安全PHP型別
- JavaScript安全的型別檢測JavaScript型別
- 最安全的型別判斷型別
- js型別轉換JS型別
- 字元型別轉換字元型別
- 3.2 型別轉換型別
- JavaScript 型別轉換JavaScript型別
- Golang型別轉換Golang型別
- 整數型別(轉)型別
- 流程的型別(轉)型別
- 型別轉換(cast)型別AST
- BigDecimal轉為String型別、int型別Decimal型別
- 日期型別與String型別的轉換型別
- 將timestamp型別轉換為date型別型別
- C++中的向上型別轉換和向下型別轉換C++型別
- 第11章 使用類——型別轉換(二)將自定義型別轉換為內建型別型別
- C++基本資料型別及型別轉換C++資料型別
- “SSH”讓遠端控制更安全(轉)
- 型別安全的 Go HTTP 請求型別GoHTTP
- Kotlin——初級篇(六): 可空型別、空安全、非空斷言、型別轉換等特性總結Kotlin型別
- 【轉】ORACLE資料型別Oracle資料型別
- interface{} 型別的轉換型別
- JNI常用型別轉換型別
- 資料型別轉換資料型別
- go interface{}型別轉換Go型別