4
votes

What happens when the assign operator := gets overloaded in Object Pascal? I mainly mean what gets evaluated first and more importantly how (if possible) can I change this order. Here is an example that bugs me:

I declare TMyClass thusly:

TMyClass = class
  private
    FSomeString: string;
    class var FInstanceList: TList;
  public
    function isValid: boolean;
    property SomeString: String write setSomeString;
  end;

the isValid function checks MyObject for nil and dangling pointers.

Now lets assume I want to overload the := operator to assign a string to TMyClass. I also want to check if the object I'm assigning this string to is a valid object and if not create a new one, so:

operator :=(const anewString: string): TMyClass;
  begin
    if not(result.isValid) then
      result:= TMyObject.Create;
    result.SomeString:= aNewString;
  end;

In short I was hoping that the result would automatically hold the pointer to the object I'm assigning to. But tests with the following:

procedure TForm1.TestButtonClick(Sender: TObject);
  var
    TestObject: TMyObject;
  begin
    TestObject:= TMyObject.Create;
    TestObject:= 'SomeString';
    TestObject.Free;
  end;

led me to believe that instead an intermediate value for result is assigned first and the actual assignment to TestObject happens after the code in := executes.

Everything I know about coding is self taught but this example shows that I clearly missed some basic concept somewhere.

I understand that there are easier ways to do this than by overloading a := operator but out of scientific curiosity is there ANY way to make this code work? (No matter how complicated.)

2
This is clearly visible in the syntax. Result is not a reference, so it is a temporary value to be filled by you. And no I don't think this can be done by operator overloading. Maybe with a default property. - Marco van de Voort

2 Answers

4
votes

It's not possible to do what you want with operator overloads. You must use a method.

The problem is that the := operator does not give you the access to the left hand side (LHS) argument (here it's the Self, a pointer to the current instance) but only to the right hand side argument.

Currently in you example if not(result.isValid) then is dangereous because the result at the beginning of the function is undefined (it can have any value, it can be either nil or not and when not nil, calling isValid will lead to some possible violation access. It does not represent the LHS at all.

Using a regular method you would have an access to the Self and you could call isValid.

2
votes

I do not have Lazarus to check, but it is possible in Delphi in the following way. We give access to an instance of the class indirectly via TValue.

Here is a sample class:

  type
  TMyClass = class(TComponent)
  private
    FSomeString: string;
  published
    property SomeString: string read FSomeString write FSomeString;
  end;

And we do the following in the container class (for example, TForm1).

  TForm1 = class(TForm)
  private
    FMyClass: TMyClass;
    function GetMyTypeString: TValue;
    procedure SetMyTypeString(const Value: TValue);
  public   
    property MyClass: TValue read GetMyTypeString write SetMyTypeString;
  end;

...

function TForm1.GetMyTypeString: TValue;
begin
  Result := FMyClass;
end;

procedure TForm1.SetMyTypeString(const Value: TValue);
begin
  if Value.Kind in [TTypeKind.tkChar, TTypeKind.tkUString,
    TTypeKind.tkString, TTypeKind.tkWChar, TTypeKind.tkWString]
  then
  begin
  if not Assigned(FMyClass) then
    FMyClass := TMyClass.Create(self);
  FMyClass.SomeString := Value.AsString;
  end else
    if Value.Kind = TTypeKind.tkClass then
      FMyClass := Value.AsType<TMyClass>;
end;

In this case both button clicks will work properly. In other words, it simulates := overloading:

procedure TForm1.Button1Click(Sender: TObject);
begin
  MyClass := 'asd';
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  MyClass := TMyClass.Create(self);
end;

And here is how to get access to TMyClass instance:

procedure TForm1.Button3Click(Sender: TObject);
begin
  if Assigned(TMyClass(MyClass.AsObject)) then
    ShowMessage(TMyClass(MyClass.AsObject).SomeString)
  else
    ShowMessage('nil');
end;