Глубокая копия записи с R1: = R2, или есть хороший способ реализовать матрицу NxM с записью? - PullRequest
12 голосов
/ 07 декабря 2010

Я реализую матрицу 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;

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

Но с динамическим массивом значения нельзя (глубоко) скопировать с помощью 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 дает функцию, получающую счетчик ссылок самого динамического массива.

Решение Барри более независимо от внутреннего импл и может работать на будущих 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;

Ответы [ 3 ]

9 голосов
/ 07 декабря 2010

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

4 голосов
/ 07 декабря 2010

Вы не можете переопределить назначение записи с помощью неявных или явных операторов.Лучшее, что вы можете сделать, - это не использовать прямое назначение, используя вместо этого метод 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;

ex

M1.Assign(M2);

вместо

M1:= M2;
3 голосов
/ 08 декабря 2010

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

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

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

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

...