Jerry Dodge
Jerry Dodge

Reputation: 27286

Two records sharing the same values?

I asked another question about using records with operators. During testing, I've discovered an anomaly where two instances of this type seem to share the same memory.

The record has an array of Integer...

type
  TVersion = record
    Values: array of Integer;
    function Count: Integer;
    class operator implicit(aVersion: TVersion): String;
    class operator implicit(aVersion: String): TVersion;
  end;

class operator TVersion.implicit(aVersion: TVersion): String;
var
  X: Integer;
begin
  Result:= '';
  for X := 0 to Length(aVersion.Values)-1 do begin
    if X > 0 then Result:= Result + '.';
    Result:= Result + IntToStr(aVersion.Values[X]);
  end;
end;

class operator TVersion.implicit(aVersion: String): TVersion;
var
  S, T: String;
  I: Integer;
begin
  S:= aVersion + '.';
  SetLength(Result.Values, 0);
  while Length(S) > 0 do begin
    I:= Pos('.', S);
    T:= Copy(S, 1, I-1);
    Delete(S, 1, I);
    SetLength(Result.Values, Length(Result.Values)+1);
    Result.Values[Length(Result.Values)-1]:= StrToIntDef(T, 0);
  end;
end;

function TVersion.Count: Integer;
begin
  Result:= Length(Values);
end;

Now I try to implement this...

var
  V1, V2: TVersion;
begin
  V1:= '1.2.3.4';
  V2:= V1;
  ShowMessage(V1);
  ShowMessage(V2);
  V2.Values[2]:= 8;
  ShowMessage(V1);
  ShowMessage(V2);
end;

I expect that the V2 should be 1.2.8.4 and V1 to remain 1.2.3.4. However, V1 also changes to 1.2.8.4.

How do I keep these records independent when I assign them?

Upvotes: 3

Views: 174

Answers (1)

David Heffernan
David Heffernan

Reputation: 613232

This behaviour is by design. A dynamic array variable is a pointer. When you assign a dynamic array variable you take a copy of the pointer and increase the reference count. Of course this also happens when you assign compound structures that contain dynamic arrays.

The documentation covers this:

If X and Y are variables of the same dynamic-array type, X := Y points X to the same array as Y. (There is no need to allocate memory for X before performing this operation.) Unlike strings and static arrays, copy-on-write is not employed for dynamic arrays, so they are not automatically copied before they are written to.

To get a mental model for this, this of dynamic arrays as being references in the same way as classes and interfaces. This is in contrast to simple types (integer, double etc.), strings, records, etc.

The standard way to deal with this is as follows:

  1. Make the dynamic array private.
  2. Expose the contents of the array through properties.
  3. In the element setter, make sure that the reference to the dynamic array is unique.

That last step is the tricky part. Make a dynamic array reference unique by calling SetLength:

SetLength(arr, Length(arr));

One of the promises that SetLength makes is that it will always make its first argument be unique.

This has the effect of implementing copy-on-write. To the best of my knowledge it is not possible to implement copy-on-assign because the compiler gives you no hook into the assignment operator.

So the answer to:

How do I keep these records independent when I assign them?

is that you cannot. You need to use copy-on-write instead.

Upvotes: 4

Related Questions