开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜