我的记录看起来像:
- TBigint = record
- PtrDigits: Pointer; <-- The data is somewhere else.
- Size: Byte;
- MSB: Byte;
- Sign: Shortint;
- ...
- class operator Implicit(a: TBigint): TBigint; <<-- is this allowed?
- ....
我知道数据应该存储在一个动态的byte数组中,但我不想更改代码,因为所有的东西都在x86-assembly中.
- procedure test(a: TBignum);
- var b: TBignum;
- begin
- b:= a; <<-- naive copy will tangle up the `PtrDigit` pointers.
- ....
- class operator TBigint.Implicit(a: TBigint): TBigint;
- begin
- sdpBigint.CreateBigint(Result,a.Size);
- sdpBigint.CopyBigint(a,Result);
- end;
(如果按预期工作,将测试并添加答案).
解决方法
我的
first answer试图阻止重写赋值运算符的想法.我仍然坚持这个答案,因为很多问题都可以用对象更好地解决.
但是,David非常正确地指出TBigInt是作为利用运算符重载的记录实现的.即a:= b c;.这是坚持基于记录的实现的一个非常好的理由.
因此,我提出了这种替代解决方案,可以一石二鸟:
>它消除了我在其他答案中解释的内存管理风险.
>并提供了一种实现Copy-on-Write语义的简单机制.
(我仍然建议除非有充分的理由保留基于记录的解决方案,否则请考虑切换到基于对象的解决方案.)
总体思路如下:
>定义一个表示BigInt数据的接口. (这最初可以是极简主义的,并且仅支持指针的控制 – 如我的示例所示.这将使现有代码的初始转换更容易.)
>定义将由TBigInt记录使用的上述接口的实现.
>接口解决了第一个问题,因为接口是托管类型;当记录超出范围时,Delphi将取消引用该接口.因此,当不再需要时,底层对象将自我毁灭.
>界面还提供了解决第二个问题的机会,因为我们可以检查RefCount以了解我们是否应该写入Copy-On.
>请注意,从长期来看,将一些BigInt实现从记录转移到类&接口.
下面的代码是精简的“大型int”实现,纯粹是为了说明这些概念. (即,“大”整数仅限于常规的32位数,并且仅实现了添加.)
- type
- IBigInt = interface
- ['{1628BA6F-FA21-41B5-81C7-71C336B80A6B}']
- function GetData: Pointer;
- function GetSize: Integer;
- procedure Realloc(ASize: Integer);
- function RefCount: Integer;
- end;
- type
- TBigIntImpl = class(TInterfacedObject,IBigInt)
- private
- FData: Pointer;
- FSize: Integer;
- protected
- {IBigInt}
- function GetData: Pointer;
- function GetSize: Integer;
- procedure Realloc(ASize: Integer);
- function RefCount: Integer;
- public
- constructor CreateCopy(ASource: IBigInt);
- destructor Destroy; override;
- end;
- type
- TBigInt = record
- PtrDigits: IBigInt;
- constructor CreateFromInt(AValue: Integer);
- class operator Implicit(AValue: TBigInt): Integer;
- class operator Add(AValue1,AValue2: TBigInt): TBigInt;
- procedure Add(AValue: Integer);
- strict private
- procedure CopyOnWriteSharedData;
- end;
- { TBigIntImpl }
- constructor TBigIntImpl.CreateCopy(ASource: IBigInt);
- begin
- Realloc(ASource.GetSize);
- Move(ASource.GetData^,FData^,FSize);
- end;
- destructor TBigIntImpl.Destroy;
- begin
- FreeMem(FData);
- inherited;
- end;
- function TBigIntImpl.GetData: Pointer;
- begin
- Result := FData;
- end;
- function TBigIntImpl.GetSize: Integer;
- begin
- Result := FSize;
- end;
- procedure TBigIntImpl.Realloc(ASize: Integer);
- begin
- ReallocMem(FData,ASize);
- FSize := ASize;
- end;
- function TBigIntImpl.RefCount: Integer;
- begin
- Result := FRefCount;
- end;
- { TBigInt }
- class operator TBigInt.Add(AValue1,AValue2: TBigInt): TBigInt;
- var
- LSum: Integer;
- begin
- LSum := Integer(AValue1) + Integer(AValue2);
- Result.CreateFromInt(LSum);
- end;
- procedure TBigInt.Add(AValue: Integer);
- begin
- CopyOnWriteSharedData;
- PInteger(PtrDigits.GetData)^ := PInteger(PtrDigits.GetData)^ + AValue;
- end;
- procedure TBigInt.CopyOnWriteSharedData;
- begin
- if PtrDigits.RefCount > 1 then
- begin
- PtrDigits := TBigIntImpl.CreateCopy(PtrDigits);
- end;
- end;
- constructor TBigInt.CreateFromInt(AValue: Integer);
- begin
- PtrDigits := TBigIntImpl.Create;
- PtrDigits.Realloc(SizeOf(Integer));
- PInteger(PtrDigits.GetData)^ := AValue;
- end;
- class operator TBigInt.Implicit(AValue: TBigInt): Integer;
- begin
- Result := PInteger(AValue.PtrDigits.GetData)^;
- end;
在构建提出的解决方案时,编写了以下测试.他们证明:一些基本功能,即写时复制按预期工作,并且没有内存泄漏.
- procedure TTestCopyOnWrite.TestCreateFromInt;
- var
- LBigInt: TBigInt;
- begin
- LBigInt.CreateFromInt(123);
- CheckEquals(123,LBigInt);
- //Dispose(PInteger(LBigInt.PtrDigits)); //I only needed this until I
- //started using the interface
- end;
- procedure TTestCopyOnWrite.TestAssignment;
- var
- LValue1: TBigInt;
- LValue2: TBigInt;
- begin
- LValue1.CreateFromInt(123);
- LValue2 := LValue1;
- CheckEquals(123,LValue2);
- end;
- procedure TTestCopyOnWrite.TestAddMethod;
- var
- LValue1: TBigInt;
- begin
- LValue1.CreateFromInt(123);
- LValue1.Add(111);
- CheckEquals(234,LValue1);
- end;
- procedure TTestCopyOnWrite.TestOperatorAdd;
- var
- LValue1: TBigInt;
- LValue2: TBigInt;
- LActualResult: TBigInt;
- begin
- LValue1.CreateFromInt(123);
- LValue2.CreateFromInt(111);
- LActualResult := LValue1 + LValue2;
- CheckEquals(234,LActualResult);
- end;
- procedure TTestCopyOnWrite.TestCopyOnWrite;
- var
- LValue1: TBigInt;
- LValue2: TBigInt;
- begin
- LValue1.CreateFromInt(123);
- LValue2 := LValue1;
- LValue1.Add(111); { If CopyOnWrite,then LValue2 should not change }
- CheckEquals(234,LValue1);
- CheckEquals(123,LValue2);
- end;
编辑
添加了一个测试,证明使用TBigInt作为过程的值参数.
- procedure TTestCopyOnWrite.TestValueParameter;
- procedure CheckValueParameter(ABigInt: TBigInt);
- begin
- CheckEquals(2,ABigInt.PtrDigits.RefCount);
- CheckEquals(123,ABigInt);
- ABigInt.Add(111);
- CheckEquals(234,ABigInt);
- CheckEquals(1,ABigInt.PtrDigits.RefCount);
- end;
- var
- LValue: TBigInt;
- begin
- LValue.CreateFromInt(123);
- CheckValueParameter(LValue);
- end;