Mariusz Schimke
Mariusz Schimke

Reputation: 3295

Can you overload the assignment operator for Delphi records?

I have a record that contains a dynamic array. It is normal that when you assign one array variable to another, in fact only the pointer to that array is assigned. This means that when you do that, both the variables point to the same array until you change the size of one of them. Therefore, when I want to assign a separate copy of an array to a variable, I use the Copy() function.

In this case, however, my array is a field of a record:

  TMyRec = record
    Value: integer;
    &Array: array of integer;
  end;

When I declare two variables of type TMyRec and then assign one to another, the "Array" fields in both of the records will be pointing to the same address in memory.

To solve that sort of problem I decided to overload the assign operator as follows:

TMyRec = record
  Value: integer;
  &Array: array of integer;
public
  class operator Implicit(Value: TMyRec): TMyRec;
end;

class operator TMyRec.Implicit(Value: TMyRec): TMyRec;
begin
  Result := Value;
  Result.&Array := Copy(Value.&Array);
end;

If this worked, I wouldn't have to copy all array fields in my records separately after assigning TMyRecord variables one to another.

Here is what I do:

var
  Rec1, Rec2: TMyRec;
begin
  Rec1.Value := 10;
  SetLength(Rec1.Array, 1);

  //I expected the "Implicit" method to be invoked here (but it is not...)
  Rec2 := Rec1;

  //if I do that, the Rec1.Array[0] will also be changed to 1 - I don't want that to happen
  Rec2.Array[0] := 1;
end;

Is there a way to make my operator overload work as I want it to? The thing is that I'm trying to overload the default assignment operator. Isn't that possible?

Upvotes: 3

Views: 3365

Answers (3)

Christophe.F
Christophe.F

Reputation: 31

You can use a pointer of TMyRec :

pMyRec = ^TMyRec;
TMyRec = record
  Value: integer;
  _Array: array of integer;
public
  class operator Implicit(Value: pMyRec): TMyRec;
end;

class operator TMyRec.Implicit(Value: pMyRec): TMyRec;
begin
  Result := Value^;
  Result._Array := Copy(Value^._Array);
end;

procedure TForm1.Button1Click(Sender: TObject);
var R1, R2 : TMyRec;
begin
  R1.Value := 1;
  SetLength( R1._Array, 2);
  R1._Array[0] := 1;
  R1._Array[1] := 2;

  R2 := @R1;
  R2._Array[0] := 3;
end;

Upvotes: 3

Brian Frost
Brian Frost

Reputation: 13454

Dont do a simple assign. Go for readability. Make your own 'RecCopy' procedure like:

procedure RecCopy( const AFrom, ATo : TMyRec );
var
  I : integer;
begin
  ATo.Value := AFrom.Value;
  SetLength( ATo.Array, Length( AFrom.Array );
  For I := 0 to Length( AFrom.Array )-1 do
    ATo.Array[I] := AFrom.Array[I];
end;

Note that you can use a better way of implmenting the array copy :-) But you see my point.

Upvotes: 2

Barry Kelly
Barry Kelly

Reputation: 42152

You could put your array inside an object instance which implements an interface, and store a reference to that interface inside the record. That way, when you try to assign to the array via the interface (and thus via the object), it can check its reference count and help with copy-on-write semantics.

Alternatively, you could use type-unsafe tricks to find out the reference count of the array, and manually copy before write if multiple references exist; but that would be unsupported and would break if the dynamic array implementation changed.

I wrote up how to implement a copy-on-write array structure wrapped up in a record. It's implemented as a generic type, but nothing stops you from using a concrete array type instead, it just won't be as general.

Upvotes: 7

Related Questions