我的记录如下:
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;
(如果按预期工作,将测试并添加答案)。
答案 0 :(得分:4)
我的first answer试图劝阻不要覆盖赋值运算符。我仍然坚持这个答案,因为很多问题都可以用对象更好地解决。
然而,David非常正确地指出TBigInt
是作为记录来实现的,以利用运算符重载。即a := b + c;
。这是坚持基于记录的实施的一个非常好的理由。
因此,我提出了这种替代解决方案,一举两得:
(我仍然建议除非有充分理由保留基于记录的解决方案,否则请考虑切换到基于对象的解决方案。)
总体思路如下:
TBigInt
记录使用。RefCount
以了解我们是否应该写入“写入”。以下代码被修剪为“big 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;
答案 1 :(得分:2)
Delphi中没有任何内容允许您挂钩分配过程。 Delphi没有像C ++拷贝构造函数那样的东西。
您的要求是:
满足这两个要求的唯一类型是本机Delphi字符串类型。这是作为参考实现的。但是他们所拥有的写时复制行为赋予它们价值语义。由于您需要一个字节数组,AnsiString是满足您需求的字符串类型。
另一种选择是简单地使你的类型不可变。这样可以让您不再担心复制引用,因为引用的数据永远不会被修改。
答案 2 :(得分:1)
在我看来,你的TBigInt
应该是一个阶级而不是一个记录。因为你担心PtrDigits被纠缠在一起,所以听起来你需要额外的内存管理才能引用指针。由于记录不支持析构函数,因此不会自动管理该内存。此外,如果您只是声明变量TBigInt
,但不调用CreatBigInt
构造函数,则内存未正确初始化。同样,这是因为您无法覆盖记录的默认无参数构造函数。
基本上,您必须始终记住为记录分配的内容,并记住手动取消分配。当然,您可以在记录中使用解除分配程序来帮助解决这个问题,但是您仍然需要记住在正确的位置调用它。
但是,您可以实现明确的Copy
功能,并在代码审核核对清单中添加一项TBitInt
已正确复制的项目。不幸的是,您必须非常小心隐含的副本,例如通过值参数将记录传递给另一个例程。
以下代码说明了一个概念上与您的需求相似的示例,并演示了CreateCopy
函数如何“解开”指针。它还强调了一些出现的内存管理问题,这就是为什么记录可能不是一个好的方法。
type
TMyRec = record
A: PInteger;
function CreateCopy: TMyRec;
end;
function TMyRec.CreateCopy: TMyRec;
begin
New(Result.A);
Result.A^ := A^;
end;
var
R1, R2: TMyRec;
begin
New(R1.A); { I have to manually allocate memory for the pointer
before I can use the reocrd properly.
Even if I implement a record constructor to assist, I
still have to remember to call it. }
R1.A^ := 1;
R2 := R1;
R2.A^ := 2; //also changes R1.A^ because pointer is the same (or "tangled")
Writeln(R1.A^);
R2 := R1.CreateCopy;
R2.A^ := 3; //Now R1.A is different pointer so R1.A^ is unchanged
Writeln(R1.A^);
Dispose(R1.A);
Dispose(R2.A); { <-- Note that I have to remember to Dispose the additional
pointer that was allocated in CreateCopy }
end;
简而言之,似乎你正在尝试将大锤唱片做成他们不太适合做的事情。
他们擅长制作精确的副本。它们具有简单的内存管理:声明一个记录变量,并分配所有内存。变量超出范围,所有内存都被释放。
如何覆盖赋值运算符可能会导致内存泄漏。
var
LBigInt: TBigInt;
begin
LBigInt.SetValue(123);
WriteBigInt(LBigInt); { Passing the value by reference or by value depends
on how WriteBigInt is declared. }
end;
procedure WriteBigInt(ABigInt: TBigInt);
//ABigInt is a value parameter.
//This means it will be copied.
//It must use the overridden assignment operator,
// otherwise the point of the override is defeated.
begin
Writeln('The value is: ', ABigInt.ToString);
end;
//If the assignment overload allocated memory, this is the only place where an
//appropriate reference exists to deallocate.
//However, the very last thing you want to do is have method like this calling
//a cleanup routine to deallocate the memory....
//Not only would this litter your code with extra calls to accommodate a
//problematic design, would also create a risk that a simple change to taking
//ABigInt as a const parameter could suddenly lead to Access Violations.