重载 Object Pascal 的赋值运算符

4
当对象Pascal中赋值运算符“:=”被重载时会发生什么?我主要是指哪个先计算,更重要的是如何(如果可能)改变这个顺序。这里有一个让我困扰的例子:
我这样声明TMyClass:
TMyClass = class
  private
    FSomeString: string;
    class var FInstanceList: TList;
  public
    function isValid: boolean;
    property SomeString: String write setSomeString;
  end;

isValid函数检查MyObject是否为nil,以及悬空指针。

现在假设我想重载:=运算符来将字符串分配给TMyClass。我还想检查我要分配这个字符串的对象是否是有效的对象,如果无效,则创建一个新对象,所以:

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

简而言之,我希望结果能自动保留我所分配对象的指针。但是通过以下测试:

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

我认为实际上会先为result中间变量赋值,然后在:=代码执行后才将其分配给TestObject,这是我的理解。

我的编程知识都是自学的,但这个例子表明我显然在某些基本概念方面错过了一些东西。

我知道有比重载:=操作符更简单的方法来做到这一点,但出于科学好奇心,是否有任何方法使此代码工作?(无论多么复杂。)


这在语法中显然可见。结果不是一个引用,所以它是一个临时值,需要您填写。我不认为这可以通过运算符重载来完成。也许可以使用默认属性。 - Marco van de Voort
2个回答

4

使用运算符重载无法实现你想要的操作,必须使用一个方法。

问题在于 := 运算符不能让你访问到左侧(LHS)参数(这里是Self,当前实例的指针),它只能访问到右侧参数。

目前在你的示例中,if not(result.isValid) then是危险的,因为函数开头的结果是未定义的(它可以有任何值,可以是nil或非nil,在不是nil时,调用isValid会导致一些可能的违规访问。它根本不代表LHS。

使用常规方法,你将可以访问Self并调用isValid


谢谢。这正是我所想的。但这是否意味着没有办法获取LHS?使用“inline”关键字怎么样?或者也许使用一些低级汇编代码?(对我来说,汇编语言就像纯魔法,也许它可以在这里有所帮助。)当然,你是对的,常规方法更有意义,但仍然……引用计数做了一些类似于我想做的事情,我认为? - N00BKING
是的,那就是意思。在Object Pascal中进行运算符重载并不像其他语言那样好。我特别想到另一种方法,其中重载直接编写为成员函数,因此_self_始终可用。您也可以尝试在FPC邮件列表或lazarus-ide.org论坛上提问,也许有人会在那里给出更好的答案。 - Abstract type
@N00BKING 我认为你想要尝试的重载方式会使代码行为变得模糊。在其他语言中,比如 Ruby,你可以使用 x = x || y 或简写为 x ||= y 来实现你想要的效果,它的意思是如果 xnil,则将 y 赋值给 x。然而,正如 Nestedtype 指出的那样,Pascal 不允许你创建自己的运算符,这实际上是一种更好、更清晰的方法,在我看来。你只能手动完成它,或者编写一个非常小的函数或方法来完成它,这也可以清楚地表达出行为。 - lurker

2

我不具备检查Lazarus的能力,但在Delphi中可以通过以下方式实现。我们通过TValue间接地访问类的实例。

这是一个示例类:

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

在容器类(例如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;

在这种情况下,两个按钮点击都会正常工作。换句话说,它模拟了:=重载的功能:
procedure TForm1.Button1Click(Sender: TObject);
begin
  MyClass := 'asd';
end;

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

以下是获取 TMyClass 实例的方法:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接