VCL 的 TList 是指標容器,內部以 void** 陣列儲存,提供動態擴充、插入刪除、排序等功能。但 void* 犧牲了型別安全:
reinterpret_cast 不做檢查,易導致存取違規Clear() 僅釋放指標陣列,**不呼叫物件解構函式**,造成記憶體洩漏delete void* 不會呼叫解構函式 (未定義行為)本文展示如何用 **模板包裝器** 在編譯期消除型別錯誤,並整合 **自動釋放機制**,同時保持零額外開銷 (inline)。
// TypedList.h
#ifndef TypedListH
#define TypedListH
#include <Classes.hpp>
#include <SysUtils.hpp>
template <class T>
class TTypedList : public TList
{
private:
bool FAutoDelete; // 是否自動刪除物件
protected:
// 型別安全的存取器
T* __fastcall Get(int Index) const {
return static_cast<T*>(TList::Get(Index));
}
void __fastcall Put(int Index, T* Item) {
TList::Put(Index, Item);
}
public:
__fastcall TTypedList(bool AutoDelete = false)
: TList(), FAutoDelete(AutoDelete) {}
// 無需自定解構函式,交由 Clear() 處理
// 型別安全的介面
int __fastcall Add(T* Item) {
return TList::Add(Item);
}
void __fastcall Delete(int Index) {
if (FAutoDelete) delete Get(Index);
TList::Delete(Index);
}
void __fastcall Clear() {
if (FAutoDelete) {
for (int i = 0; i < Count; ++i)
delete Get(i); // 型別正確,呼叫正確解構函式
}
TList::Clear();
}
T* __fastcall First() const { return Get(0); }
T* __fastcall Last() const { return Get(Count - 1); }
int __fastcall IndexOf(T* Item) const { return TList::IndexOf(Item); }
void __fastcall Insert(int Index, T* Item) { TList::Insert(Index, Item); }
int __fastcall Remove(T* Item) {
int idx = TList::Remove(Item);
if (FAutoDelete && idx != -1) delete Item;
return idx;
}
// 型別安全的索引子屬性
__property T* Items[int Index] = {read=Get, write=Put};
};
#endif
| 設計點 | 說明 |
|---|---|
| 繼承 TList | 復用既有動態陣列、排序、搜尋邏輯,零成本抽象 |
| static_cast 而非 reinterpret_cast | 編譯期型別檢查,確保 T* 相容 |
| FAutoDelete 旗標 | 建構時決定擁有權語意,RAII 風格 |
| Delete/Clear/Remove 同步釋放 | 所有移除路徑皆處理物件刪除,無遺漏 |
| __property Items | 提供 list->Items[i] 型別安全存取,語法自然 |
| inline 方法 | 標頭檔實作,編譯器內聯展開,零函式呼叫開銷 |
#include "TypedList.h"
void __fastcall TForm1::CreateButtons()
{
typedef TTypedList<TButton> TButtonList;
TButtonList* btnList = new TButtonList(false); // 表單負責釋放
for (int i = 0; i < 5; ++i) {
TButton* btn = new TButton(this);
btn->Caption = "Button " + IntToStr(i);
btn->Top = 50 + i * 30;
btn->Parent = this;
btnList->Add(btn);
}
// 型別安全存取
for (int i = 0; i < btnList->Count; ++i)
btnList->Items[i]->Width = 120;
// btnList->Add(Application); // 編譯錯誤:TApplication* 不能轉 TButton*
delete btnList; // 僅刪除容器,不刪除按鈕
}
void __fastcall TForm1::OpenDataSets()
{
typedef TTypedList<TDataSet> TDataSetList;
TDataSetList* dsList = new TDataSetList(true); // 容器負責釋放
dsList->Add(new TTable(this));
dsList->Add(new TQuery(this));
dsList->Add(new TADOTable(this));
for (int i = 0; i < dsList->Count; ++i)
dsList->Items[i]->Active = true;
delete dsList; // 自動刪除所有 TDataSet 衍生物件
}
void __fastcall TForm1::ManageControls()
{
typedef TTypedList<TControl> TControlList; // 基類指標容器
TControlList* ctrlList = new TControlList(true);
ctrlList->Add(new TButton(this));
ctrlList->Add(new TEdit(this));
ctrlList->Add(new TComboBox(this));
for (int i = 0; i < ctrlList->Count; ++i)
ctrlList->Items[i]->Visible = false;
delete ctrlList; // 正確呼叫 TControl 虛擬解構函式
}
delete base_ptr 呼叫正確解構函式的前提是 **基類解構函式為 virtual**。VCL 所有類別繼承自 TObject,其解構函式為 virtual,因此安全。若自訂類別作為基類,**務必宣告 virtual 解構函式**。
| 考量 | TTypedList | std::vector<T*> / std::unique_ptr<T> |
|---|---|---|
| VCL 整合 | 原生相容 TList 介面、元件流、RTTI | 需轉換、無 VCL 流支援 |
| 編譯器相容 | 支援舊版 BCB (C++98 模板) | 需較新編譯器支援 C++11/14 |
| 效能 | inline 零開銷 | 同樣可內聯,現代編譯器更強 |
| 記憶體安全 | 手動旗標控制 | unique_ptr 強制擁有權、例外安全 |
| 現代化 | 維護舊專案 | 新專案首選 |
// 新專案建議:標準庫 + 智慧指標
#include <vector>
#include <memory>
#include <algorithm>
using ButtonPtr = std::unique_ptr<TButton>;
std::vector<ButtonPtr> buttons;
buttons.emplace_back(new TButton(this)); // 或 make_unique (C++14)
buttons.back()->Caption = "Safe";
// 自動釋放、例外安全、移動語意、無需手動 delete
// 存取:buttons[i]->Caption (型別安全)
TTypedList<T> 以最小代價為舊專案引入 **編譯期型別安全** 與 **自動資源管理**,消除 void* 帶來的隱性 Bug。新專案請直接擁抱 std::vector<std::unique_ptr<T>>,享受現代 C++ 的完整型別系統與 RAII 保護。