Deep copy of a record with R1:=R2, or Is there good way to implement NxM matrix with record?

deep-copy, delphi, matrix, record, variable-assignment

Solution

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.

Problem

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; ```

Original source