首版實作將泛型約束限定為 T: class,僅能管理類別實例,實為「智能物件」。本文移除 class 約束,支援任意型別 (含指標),解決值型別無法賦 nil、不支援比較運算子、無統一釋放機制等核心難題,實現真正語意上的智能指針。
| 挑戰 | 原因 | 解決方案 |
|---|---|---|
fObj := nil 編譯錯誤 | T 可能為值型別 | Integer((@fObj)^) := 0 直接清零記憶體 |
aObj <> fObj 不支援 | 泛型 T 不保證有比較運算子 | Integer((@aObj)^) <> Integer((@fObj)^) 比較地址 |
if fObj = nil 不支援 | 值型別無 nil 概念 | if Integer((@fObj)^) <> 0 檢查地址非零 |
| 統一釋放機制 | T 可能是類別/指標/陣列 | 用 TypeInfo(T) 運行期判斷 Kind 執行對應釋放邏輯 |
procedure TAutoPtr<T>.FreeObj;
begin
// 獲取泛型型別資訊
if fTypeInfo = nil then
begin
// T 為無類型指標 (Pointer) - Kind = tkUnknown 或 nil
Dispose(Pointer((@fObj)^)); // 內部含 _Finalize + _FreeMem
end
else
case fTypeInfo.Kind of
tkClass:
TObject((@fObj)^).Free; // 呼叫虛擬析構函數鏈
tkArray, tkDynArray:
; // 陣列/動態陣列編譯器自動管理,無需手動釋放
// tkRecord, tkInterface 等其他類型可擴展
end;
// 統一清零
Integer((@fObj)^) := 0;
end;
{*******************************************************
* Delphi Smart Pointer Class
* AutoPtr
* Version 0.2 Beta
* Yang Qinqing @ https://www.cnblogs.com/felixyeou
*******************************************************}
unit AutoPtr;
interface
uses SysUtils, TypInfo;
type
IAutoPtr<T> = interface
['{86DB82D6-9A32-4A6A-9191-2E0DFE083C38}']
function Get: T;
function Release: T;
procedure Reset(aObj: T);
end;
TAutoPtr<T> = class(TInterfacedObject, IAutoPtr<T>)
private
fObj: T;
fTypeInfo: PTypeInfo;
procedure FreeObj;
public
class function New(aObj: T): IAutoPtr<T>; overload;
class function New: IAutoPtr<T>; overload; // 自動構造類別實例
constructor Create(aObj: T); virtual;
destructor Destroy; override;
function Get: T;
function Release: T;
procedure Reset(aObj: T);
end;
implementation
{ TAutoPtr<T> }
constructor TAutoPtr<T>.Create(aObj: T);
begin
fObj := aObj;
fTypeInfo := TypeInfo(T); // 緩存型別資訊
end;
class function TAutoPtr<T>.New(aObj: T): IAutoPtr<T>;
begin
Result := TAutoPtr<T>.Create(aObj) as IAutoPtr<T>;
end;
function TAutoPtr<T>.Release: T;
begin
Result := fObj;
Integer((@fObj)^) := 0; // 清零,不釋放物件
end;
procedure TAutoPtr<T>.Reset(aObj: T);
begin
if Integer((@aObj)^) <> Integer((@fObj)^) then
begin
FreeObj; // 釋放舊物件
fObj := aObj; // 接管新物件
end;
end;
destructor TAutoPtr<T>.Destroy;
begin
if Integer((@fObj)^) <> 0 then FreeObj;
fTypeInfo := nil;
inherited;
end;
procedure TAutoPtr<T>.FreeObj;
begin
if fTypeInfo = nil then
begin
// 無類型指標
Dispose(Pointer((@fObj)^));
end
else
case fTypeInfo.Kind of
tkClass:
TObject((@fObj)^).Free;
tkArray, tkDynArray:
; // 自動管理
end;
Integer((@fObj)^) := 0;
end;
function TAutoPtr<T>.Get: T;
begin
Result := fObj;
end;
class function TAutoPtr<T>.New: IAutoPtr<T>;
var
typInfo: PTypeInfo;
obj: TObject;
objNew: T;
begin
typInfo := TypeInfo(T);
// 僅支援類別型別自動構造
if (typInfo <> nil) and (typInfo.Kind = tkClass) then
begin
obj := GetTypeData(typInfo).ClassType.Create; // 呼叫預設建構函數
objNew := T((@obj)^); // 強制轉型
Exit(New(objNew));
end;
raise Exception.Create('只能構造 class 型別的物件。');
end;
end.
// 取得變數地址並解引用為 Integer 進行操作
Integer((@fObj)^) := 0; // 相當於 fObj := nil (對指標/類別)
Integer((@aObj)^) <> Integer((@fObj)^) // 相當於 aObj <> fObj (比較指標值)
Integer((@fObj)^) <> 0 // 相當於 fObj <> nil
原理:@fObj 取得變數地址 (型別 ^T),(@fObj)^ 解引用得到原值,Integer(...) 強制視為整數進行位元級操作。這繞過了泛型約束限制,直接操作底層記憶體。
// System.pas 中 Dispose 實作 (簡化) asm PUSH EAX CALL _Finalize // 處理受管理型別 (介面、動態陣列、字串、Variant 等) POP EAX CALL _FreeMem // 釋放記憶體塊 end;選擇 Dispose:自動處理指向受管理型別指標的終結化 (如
PString、PInterface),比單純FreeMem更安全完整。3. 自動構造 New 重載
class function TAutoPtr<T>.New: IAutoPtr<T>; var typInfo: PTypeInfo; obj: TObject; objNew: T; begin typInfo := TypeInfo(T); if (typInfo <> nil) and (typInfo.Kind = tkClass) then begin obj := GetTypeData(typInfo).ClassType.Create; // 獲取類別型別並構造 objNew := T((@obj)^); // 類別實例指標轉為泛型 T Exit(New(objNew)); end; raise Exception.Create('只能構造 class 型別的物件。'); end;用法:
var ptr := TAutoPtr<TMyClass>.New;自動構造並接管,無需手動Create。使用範例
1. 管理類別實例 (原有功能)
var ap: IAutoPtr<TMyClass>; ap := TAutoPtr<TMyClass>.New(TMyClass.Create); ap.Get.DoSomething;2. 管理指標型別 (新功能)
type PMyRecord = ^TMyRecord; var ptr: IAutoPtr<PMyRecord>; New(ptr.Get); // 分配記憶體 ptr.Get^.Field := 123; // 離開作用域自動 Dispose3. 自動構造 (新功能)
var ap: IAutoPtr<TMyClass>; ap := TAutoPtr<TMyClass>.New; // 自動 Create ap.Get.DoSomething;4. 重置指向新物件
ap.Reset(TMyClass.Create); // 自動 Free 舊物件,接管新物件5. 釋放擁有權
var obj := ap.Release; // 取回物件,智能指針不再管理 // 需手動 obj.Free局限性與未來擴展方向
| 局限 | 說明 | 可能方案 |
|---|---|---|
| 不支援記錄型別 (Record) 管理 | Record 無建構/析構,通常棧分配 | 若為堆上 Record 指標,歸屬指標管理 |
| 介面型別釋放未特殊處理 | 介面有參考計數,Free 不適用 | 擴展 tkInterface 分支:IInterface((@fObj)^) := nil |
| 動態陣列/字串指標管理 | 編譯器自動管理,但指標需 _Finalize | Dispose 已處理 _Finalize |
| 無運算子重載 | Delphi 不支援運算子重載 | 無法實現 *ptr, ptr-> 語法糖 |
| 執行期型別檢查開銷 | 每次釋放需 case Kind | 可用 RTTI 緩存或編譯期特化優化 |
通過移除 class 約束、記憶體地址直接操作、TypeInfo 運行期型別分發、Dispose 統一釋放四大核心技術,本實作實現了支援類別、指標、自動構造的真正泛型智能指針 TAutoPtr<T>。雖受限於 Delphi 語言特性 (無運算子重載、無編譯期特化),但已大幅超越首版「智能物件」,接近 C++ std::unique_ptr 的語意與功能。