问题描述
我有一条记录如下:
TBigint = record
PtrDigits: Pointer; <-- The data is somewhere else.
Size: Byte;
MSB: Byte;
Sign: Shortint;
...
class operator Implicit(a: TBigint): TBigint; <<-- is this allowed?
....
该代码是前置运算符遗留代码,但我想添加运算符.
The code is pre-class operator legacy code, but I want to add operators.
我知道数据确实应该存储在一个动态字节数组中,但是我不想更改代码,因为所有内容都在x86汇编中.
I know the data should really be stored in a dynamic array of byte, but I do not want to change the code, because all the meat is in x86-assembly.
我想要下面的代码来触发底部的类运算符:
I want to following code to trigger the class operator at the bottom:
procedure test(a: TBignum);
var b: TBignum;
begin
b:= a; <<-- naive copy will tangle up the `PtrDigit` pointers.
....
如果我将隐式类型转换添加到自身,是否将执行以下代码?
If I add the implicit typecast to itself, will the following code be executed?
class operator TBigint.Implicit(a: TBigint): TBigint;
begin
sdpBigint.CreateBigint(Result, a.Size);
sdpBigint.CopyBigint(a, Result);
end;
(如果测试符合我的预期,将对其进行测试并添加答案).
推荐答案
我的第一个答案试图劝阻该想法覆盖赋值运算符.我仍然支持该答案,因为使用对象可以更好地解决许多要遇到的问题.
My first answer attempts to dissuade against the idea of overriding the assignment operator. I still stand by that answer, because many of the problems to be encountered are better solved with objects.
但是,David非常正确地指出,TBigInt
被实现为记录以利用运算符重载. IE. a := b + c;
.这是坚持基于记录的实现的一个很好的理由.
However, David quite rightly pointed out that TBigInt
is implemented as a record to leverage operator overloads. I.e. a := b + c;
. This is a very good reason to stick with a record based implementation.
因此,我提出了另一种解决方案,用一只石头杀死两只鸟:
Hence, I propose this alternative solution that kills two birds with one stone:
- 它消除了我在其他答案中解释的内存管理风险.
- 并提供了一种简单的机制来实现写时复制语义.
(我仍然建议除非有充分的理由保留基于记录的解决方案,否则请考虑切换到基于对象的解决方案.)
总体思路如下:
- 定义一个表示BigInt数据的接口. (在我的示例中,这最初可能是最低限度的,并且仅支持对指针的控制.这将使现有代码的初始转换更加容易.)
- 定义上述接口的实现,该接口将由
TBigInt
记录使用. - 接口解决了第一个问题,因为接口是托管类型.当记录超出范围时,Delphi将取消引用该接口.因此,底层对象将在不再需要时自行销毁.
- 该界面还提供了解决第二个问题的机会,因为我们可以检查
RefCount
以了解是否应按写复制. - 请注意,从长远来看,将某些BigInt实现从记录移至类&界面.
- Define an interface to represent the BigInt data. (This can initially be minimalist and support only control of the pointer - as in my example. This would make the initial conversion of existing code easier.)
- Define an implementation of the above interface which will be used by the
TBigInt
record. - The interface solves the first problem, because interfaces are a managed type; and Delphi will dereference the interface when a record goes out of scope. Hence, the underlying object will destroy itself when no longer needed.
- The interface also provides the opportunity to solve the second problem, because we can check the
RefCount
to know whether we should Copy-On-Write. - Note that long term it might prove beneficial to move some of the BigInt implementation from the record to the class & interface.
下面的代码是经过精简的"big int"实现,仅用于说明概念. (即,大"整数仅限于常规的32位数字,并且仅实现了加法运算.)
The following code is trimmed-down "big int" implementation purely to illustrate the concepts. (I.e. The "big" integer is limited to a regular 32-bit number, and only addition has been implemented.)
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;
在我构建建议的解决方案时编写了以下测试.他们证明:一些基本功能,写时复制可以按预期工作,并且没有内存泄漏.
The following tests were written as I built up the proposed solution. They prove: some basic functionality, that the copy-on-write works as expected, and that there are no memory leaks.
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
作为值参数用于过程.
Edit
Added a test demonstrating use of TBigInt
as value parameter to a procedure.
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;
这篇关于使用类运算符是否允许对其自身进行隐式类型转换?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!