Deep copy of a record with R1:=R2, or Is there good way to implement NxM matrix with record?
I'm implementing a N x M matrix (class) with a record and an internal dynamic array like below.
TMat = record
public
// contents
_Elem: array of array of Double;
//
procedure SetSize(Row, Col: Integer);
procedure Add(const M: TMat);
procedure Subtract(const M: TMat);
function Multiply(const M: TMat): TMat;
//..
class operator Add(A, B: TMat): TMat;
class operator Subtract(A, B: TMat): TMat;
/开发者_开发问答/..
class operator Implicit(A: TMat): TMat; // call assign inside proc.
// <--Self Implicit(which isn't be used in D2007, got compilation error in DelphiXE)
procedure Assign(const M: TMat); // copy _Elem inside proc.
// <-- I don't want to use it explicitly.
end;
I choose a record, because I don't want to Create/Free/Assign to use it.
But with dynamic array, values can't be (deep-)copied with M1 := M2, instead of M1.Assign(M2).
I tried to declare self-Implicit conversion method, but it can't be used for M1:=M2.
(Implicit(const pA: PMat): TMat and M1:=@M2 works, but it's pretty ugly and unreadable..)
Is there any way to hook assignment of record ?
Or is there any suggestion to implement N x M matrix with records ?
Thanks in advance.
Edit:
I implemented like below with Barry's method and confirmed working properly.
type
TDDArray = array of array of Double;
TMat = record
private
procedure CopyElementsIfOthersRefer;
public
_Elem: TDDArray;
_FRefCounter: IInterface;
..
end;
procedure TMat.SetSize(const RowSize, ColSize: Integer);
begin
SetLength(_Elem, RowSize, ColSize);
if not Assigned(_FRefCounter) then
_FRefCounter := TInterfacedObject.Create;
end;
procedure TMat.Assign(const Source: TMat);
var
I: Integer;
SrcElem: TDDArray;
begin
SrcElem := Source._Elem; // Allows self assign
SetLength(Self._Elem, 0, 0);
SetLength(Self._Elem, Length(SrcElem));
for I := 0 to Length(SrcElem) - 1 do
begin
SetLength(Self._Elem[I], Length(SrcElem[I]));
Self._Elem[I] := Copy(SrcElem[I]);
end;
end;
procedure TMat.CopyElementsIfOthersRefer;
begin
if (_FRefCounter as TInterfacedObject).RefCount > 1 then
begin
Self.Assign(Self); // Self Copy
end;
end;
I agree it's not efficient. Just using Assign with pure record is absolutely faster.
But it's pretty handy and more readable.(and interesting. :-)
I think it's useful for light calculation or pre-production prototyping. Isn't it ?
Edit2:
kibab gives function getting reference count of dynamic array itself.
Barry's solution is more independent from internal impl, and perhaps works on upcoming 64bit compilers without any modification, but in this case, I prefer kibab's for it's simplicity & efficiency. Thanks.
TMat = record
private
procedure CopyElementsIfOthersRefer;
public
_Elem: TDDArray;
..
end;
procedure TMat.SetSize(const RowSize, ColSize: Integer);
begin
SetLength(_Elem, RowSize, ColSize);
end;
function GetDynArrayRefCnt(const ADynArray): Longword;
begin
if Pointer(ADynArray) = nil then
Result := 1 {or 0, depending what you need}
else
Result := PLongword(Longword(ADynArray) - 8)^;
end;
procedure TMat.CopyElementsIfOthersRefer;
begin
if GetDynArrayRefCnt(_Elem) > 1 then
Self.Assign(Self);
end;
You can use an interface field reference inside your record to figure out if your array is shared by more than one record: simply check the reference count on the object behind the interface, and you'll know that the data in the arrays is shared. That way, you can lazily copy on modification, but still use data sharing when the matrices aren't being modified.
You can't override record assignment by Implicit or Explicit operators. The best you can do IMO is not to use direct assignment, using M.Assign method instead:
procedure TMat.Assign(const M: TMat);
begin
// "Copy" currently only copies the first dimension,
// bug report is open - see comment by kibab
// _Elem:= Copy(M._Elem);
..
end;
ex
M1.Assign(M2);
instead of
M1:= M2;
I've just realised a reason why this may not be such a great idea. It's true that the calling code becomes much simpler with operator overloading. But you may have performance problems.
Consider, for example, the simple code A := A+B;
and suppose you use the idea in Barry's accepted answer. Using operator overloading this simple operation will result in a new dynamic array being allocated. In reality you would want to perform this operation in place.
Such in-place operations are very common in linear algebra matrix algorithms for the simple reason that you don't want to hit the heap if you can avoid it - it's expensive.
For small value types (e.g. complex numbers, 3x3 matrices etc.) then operator overloading inside records is efficient, but I think, if performance matters, then for large matrices operator overloading is not the best solution.
精彩评论