Глубокая копия записи с R1: = R2 или есть хороший способ реализовать матрицу NxM с записью?

Я реализую матрицу (класс) N x M с записью и внутренним динамическим массивом, как показано ниже.

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;

Я выбираю запись, потому что я не хочу создавать /Free/Assign, чтобы использовать ее.

Но с динамическим массивом значения не могут быть (глубокие) скопированы с M1: = M2 вместо M1.Assign(M2).

Я попытался объявить метод самопликации неявного преобразования, но он не может использоваться для M1: = M2.

(Неявный (const pA: PMat): TMat и M1: = @M2 работает, но он довольно уродливый и нечитаемый..)

Есть ли способ связать назначение записи?

Или есть ли предложение реализовать матрицу N x M с записями?

Спасибо заранее.

Edit:

Я реализовал, как показано ниже, с помощью метода Барри и подтвердил правильность работы.

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;

Я согласен, что это не эффективно. Просто использование Assign с чистой записью происходит абсолютно быстрее.

Но это довольно удобно и читаемо. (и интересно.: -)

Я думаю, что он полезен для расчета света или прототипа предварительного производства. Не правда ли?

Edit2:

kibab дает функцию, получающую счетчик ссылок самого динамического массива.

Решение Barry более независимо от внутреннего impl и, возможно, работает над предстоящими 64-битными компиляторами без каких-либо изменений, но в этом случае я предпочитаю kibab для его простоты и эффективности. Спасибо.

  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;

Ответ 1

Вы можете использовать ссылку поля интерфейса внутри своей записи, чтобы выяснить, принадлежит ли ваш массив более чем одной записи: просто проверьте количество ссылок на объект за интерфейсом, и вы узнаете, что данные в массивах является общим. Таким образом, вы можете лениво копировать изменения, но использовать обмен данными, когда матрицы не изменяются.

Ответ 2

Вы не можете переопределить назначение записи операторами Implicit или Explicit. Лучшее, что вы можете сделать IMO, - это не использовать прямое назначение, используя вместо этого метод M.Assign:

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;

ех

M1.Assign(M2);

вместо

M1:= M2;

Ответ 3

Я только что понял причину, почему это может быть не такая замечательная идея. Это правда, что код вызова становится намного проще при перегрузке оператора. Но у вас могут быть проблемы с производительностью.

Рассмотрим, например, простой код A := A+B; и предположим, что вы используете идею в принятом ответе Барри. Использование перегрузки оператора этой простой операции приведет к тому, что будет выделен новый динамический массив. В действительности вы хотели бы выполнить эту операцию на месте.

Такие операции на месте очень распространены в линейных алгоритмах матрицы алгебр по той простой причине, что вы не хотите ударять кучу, если можете избежать этого - это дорого.

Для небольших типов значений (например, комплексных чисел, матриц 3х3 и т.д.), то перегрузка оператора внутри записей эффективна, но я думаю, если производительность имеет значение, то для больших матриц перегрузка оператора не является лучшим решением.