导航:
论坛 -> DELPHI技术
斑竹:liumazi,sephil
作者:
2015/2/26 14:11:04
标题:
Delphi泛型应用示例之一:接口化指针
浏览:2140
加入我的收藏
楼主:
接口的引用计数特性可以让我们方便地创建一个接口实例,它可以到处使用而无需手动释放内存。但是假如我们这个接口里面有很多数据项那就麻烦多了,得针对每个数据项分别写出其Get和Set方法,接口要写一遍,类还要写一遍。 举个例子: 我们想把这个记录类型用接口的方式实现: TRecord_Test = record Data1: string; Data2: Integer; Data3: Double; end; 通常的方法是这样的: type TInterface_Test = interface function GetData1: string; function GetData2: Integer; function GetData3: Double; procedure SetData1(const Value: string); procedure SetData2(const Value: Integer); procedure SetData3(const Value: Double); property Data1: string read GetData1 write SetData1; property Data2: Integer read GetData2 write SetData2; property Data3: Double read GetData3 write SetData3; end; TClass_Test = class(TInterfacedObject, TInterface_Test) private FData2: Integer; FData3: Double; FData1: string; function GetData1: string; function GetData2: Integer; function GetData3: Double; procedure SetData1(const Value: string); procedure SetData2(const Value: Integer); procedure SetData3(const Value: Double); public property Data1: string read GetData1 write SetData1; property Data2: Integer read GetData2 write SetData2; property Data3: Double read GetData3 write SetData3; end; implementation { TClass_Test } function TClass_Test.GetData1: string; begin Result := FData1; end; function TClass_Test.GetData2: Integer; begin Result := FData2; end; function TClass_Test.GetData3: Double; begin Result := FData3; end; procedure TClass_Test.SetData1(const Value: string); begin FData1 := Value; end; procedure TClass_Test.SetData2(const Value: Integer); begin FData2 := Value; end; procedure TClass_Test.SetData3(const Value: Double); begin FData3 := Value; end;
----------------------------------------------
-
作者:
2015/2/26 14:11:38
1楼:
上面的代码是不是看得人吐血?要是有多个类似的记录,恐怕大家宁愿到处维护指针了。 不要紧,我们有万能的泛型。请看下述代码:
----------------------------------------------
-
作者:
2015/2/26 14:11:57
2楼:
unit BambooInterfacedPointer; interface type TValuePointer<T> = packed record PValue: ^T; end; IBambooInterfacedPointer<T> = interface function Instence: TValuePointer<T>; end; TBambooInterfacedPointer<T> = class(TInterfacedObject, IBambooInterfacedPointer<T>) strict private FValuePointer: ^T; private function Instence: TValuePointer<T>; inline; public constructor Create; destructor Destroy; override; end; implementation { TBambooInterfacedPointer<T> } constructor TBambooInterfacedPointer<T>.Create; begin inherited Create; New(FValuePointer); System.FillChar(FValuePointer^, SizeOf(T), 0); end; destructor TBambooInterfacedPointer<T>.Destroy; begin Dispose(FValuePointer); inherited Destroy; end; function TBambooInterfacedPointer<T>.Instence: TValuePointer<T>; begin Result.PValue := FValuePointer; end; end.
----------------------------------------------
-
作者:
2015/2/26 14:12:26
3楼:
说明一下,之所以要定义 type TValuePointer<T> = packed record PValue: ^T; end; 这么一个东西,是因为在泛型接口里面不允许使用如下写法 IBambooInterfacedPointer<T> = interface function Instence: ^T; end; 所以定义了一个记录类型来作为过渡。
----------------------------------------------
-
作者:
2015/2/26 14:13:23
4楼:
最后,则是我写的这个接口化指针的应用实例: type TRecord_Test = record Data1: string; Data2: Integer; Data3: Double; end; var aInterfacedPointer: IBambooInterfacedPointer<TRecord_Test>; begin aInterfacedPointer := TBambooInterfacedPointer<TRecord_Test>.Create; with aInterfacedPointer.Instence.PValue^ do begin Data1 := '123'; Data2 := 456; Data3 := 7.89; end; end; 是不是很方便啊 ? 重要的是 , 所有的数据类型都可以这么干 !
----------------------------------------------
-
作者:
2015/2/26 14:34:51
5楼:
话说,为啥用泛型了?用指针不方便? 还是说为了封装的原因,所以要写上述一堆的代码进行get/set? 我怎么就觉得用record指针方便了?特别是上述引用的示例情况,真没想到有特需要,特合适泛型使用的场合。
----------------------------------------------
-
作者:
2015/2/26 14:41:38
6楼:
我使用的封装顺序原则是: 1:数组+N函数 2:record+N个函数 3:1个类 4:N个类 使用的过程,特觉得指针是万能的,比泛型就是多了强制转换的过程(多了一个指针认识的过程),其它跟普通写法没区别。 另外一个原因是:今天用XE写码,明天去了另家公司,可能D5写码,那之前用泛型或高级语言特性的代码全部不能用。。。
----------------------------------------------
-
作者:
2015/2/26 14:56:43
7楼:
指针是万能的,维护指针是万恶的。我举的例子只是为了说明用法,不是为了证明指针接口化的优越性。代码越复杂,维护越麻烦。在多线程+数据池的情况下,我可不愿意去维护指针。
----------------------------------------------
-
作者:
2015/2/26 15:09:39
8楼:
至于D5什么的,我不觉得自己找不到能使用泛型的好公司。
----------------------------------------------
-
作者:
2015/2/27 11:55:13
9楼:
学习ing
----------------------------------------------
kittyapp
作者:
2015/2/27 22:04:36
10楼:
学习中,楼主能否再添加上一个应用实例
----------------------------------------------
m
作者:
2015/2/28 8:43:17
11楼:
4楼已经有例子了
----------------------------------------------
-
作者:
2015/2/28 10:18:51
12楼:
支持楼主,,,DELPHI 智能指针也是根据接口计数器和泛型来实现的!
----------------------------------------------
不喧哗 自有声 心静 思远 志行千里
作者:
2015/2/28 11:15:18
13楼:
楼主,,偶对 FillChar 有点担心,简单类型还行,带有生命周期的对象,可能会有内存泄漏,不知道你测试过没(比如循环1000次)。 New(FValuePointer); System.FillChar(FValuePointer^, SizeOf(T), 0);
----------------------------------------------
不喧哗 自有声 心静 思远 志行千里
作者:
2015/2/28 11:23:24
13楼:
友情提示,Instence拼写错误,应为Instance
----------------------------------------------
-
作者:
2015/2/28 11:30:01
14楼:
正是因为考虑到带有生命周期的对象,所以才在New以后用0填充。在4楼的例子里面有string,没有发生内存泄漏。你可以设置System.ReportMemoryLeaksOnShutdown := True;来测试。
----------------------------------------------
-
作者:
2015/2/28 11:31:30
15楼:
谢谢souledge的提醒!
----------------------------------------------
-
作者:
2015/2/28 11:41:51
14楼:
Instence是Instance的笔误吗?
----------------------------------------------
-
作者:
2015/2/28 11:49:37
16楼:
to kkkmmm: 是的,笔误。
----------------------------------------------
-
作者:
2015/2/28 12:24:18
17楼:
内存泄漏不会发生在FillChar的时候,但是有另一种内存泄漏的可能性,那就是T里面又包含了需要手动释放的数据,比如TObject或者Pointer。比如: type TRecord_Test = record Data1: string; Data2: Integer; Data3: Double; Data4: Pointer; end; 如果你对Data4执行了GetMem,在FreeMem之前就结束了接口的生命周期,那么会发生内存泄漏。
----------------------------------------------
-
作者:
2015/2/28 12:26:51
18楼:
为了避免这种未内存泄漏,我们可以给TBambooInterfacedPointer加一个方法在Dispose之前检测T里面未释放的内存: unit BambooInterfacedPointer; interface uses System.SysUtils; type TValuePointer<T> = packed record PValue: ^T; end; IBambooInterfacedPointer<T> = interface function Instance: TValuePointer<T>; end; TBambooInterfacedPointer<T> = class(TInterfacedObject, IBambooInterfacedPointer<T>) strict private FValuePointer: ^T; FProc_BeforeDispose: TProc<TValuePointer<T>>; private function Instance: TValuePointer<T>; inline; public constructor Create(const aProc_BeforeDispose: TProc < TValuePointer < T >> = nil); destructor Destroy; override; end; implementation { TBambooInterfacedPointer<T> } constructor TBambooInterfacedPointer<T>.Create(const aProc_BeforeDispose: TProc < TValuePointer < T >> ); begin inherited Create; FProc_BeforeDispose := aProc_BeforeDispose; New(FValuePointer); System.FillChar(FValuePointer^, SizeOf(T), 0); end; destructor TBambooInterfacedPointer<T>.Destroy; begin if Assigned(FProc_BeforeDispose) then FProc_BeforeDispose(Instance); Dispose(FValuePointer); inherited Destroy; end; function TBambooInterfacedPointer<T>.Instance: TValuePointer<T>; begin Result.PValue := FValuePointer; end; end.
----------------------------------------------
-
作者:
2015/2/28 12:30:21
19楼:
相应的例子如下(假定Data4都是通过GetMem初始化的): type TRecord_Test = record Data1: string; Data2: Integer; Data3: Double; Data4: Pointer; end; procedure TForm3.Button1Click(Sender: TObject); var aInterfacedPointer: IBambooInterfacedPointer<TRecord_Test>; i: Integer; begin for i := 1 to 100000 do begin aInterfacedPointer := TBambooInterfacedPointer<TRecord_Test>.Create( procedure(aValuePointer: TValuePointer<TRecord_Test>) begin with aValuePointer.PValue^ do if Assigned(Data4) then FreeMem(Data4); end); with aInterfacedPointer.Instance.PValue^ do begin Data1 := '123'; Data2 := 456; Data3 := 7.89; GetMem(Data4, 1024); end; end; end;
----------------------------------------------
-