为什么在Delphi中可以进行自我分配?

17

这段GUI应用程序的代码可以编译并运行:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Self := TForm1.Create(Owner);
end;

(在Delphi 6和2009中测试过)

  • 为什么Self是可写的而不是只读的?
  • 在哪些情况下这可能有用?

编辑:

  • 在Delphi Prism中是否也可以实现?(我认为可以,参见这里

更新: 使用Self赋值的Delphi应用程序/库:

5个回答

11

这并不像你认为的那么糟糕。我刚在 Delphi 2009 中进行了测试,看起来虽然 Self 参数没有使用 const 语义,但也没有使用 var 语义,因此你可以在方法内部随意更改它,而不会失去调用者对对象的引用。这将是一件非常糟糕的事情。

至于原因,可能是疏忽大意,或者像 Marco 所建议的那样:允许你将 Self 传递给一个var参数。


7
也许是为了允许传递给const或var参数?这可能是一个人为的产物,因为系统在:=符号左侧没有任何self。

1
传递self作为const不需要特殊的允许,因为Self也可以是const。将其作为Var参数传递只是另一种赋值形式,问题仍然存在。 - H H
@Henk Holterman:也许这只是为了方便起见:想象一下,有一个函数(来自某个库),即使你知道它不会实际更改对象引用,也要求将对象引用作为变量参数。如果self不可分配,您将不得不声明一个额外的变量,将self分配给它并传递该变量。由于self是可分配的,因此可以将self直接传递给函数。 - dummzeuch

5

将值分配给自身是非常不合逻辑且无用的,这种“特性”可能是一个疏忽。而且,与可分配常量一样,纠正此类问题并不总是容易的。

这里的简单建议是:不要这样做。


2
实际上,“Self”只是一个名称引用,指向堆中对象的地址在栈上的存储位置。强制将此变量设为只读是可能的,但设计者显然决定不这样做。我认为这种决定是随意的。
我看不出有什么用处,这只会改变堆栈中的值。而且,更改此值可能很危险,因为无法保证引用实例成员的代码的行为在编译器版本之间是一致的。
更新:针对PatrickvL的评论
“变量”Self不在栈上(据我所知,它从来没有在栈上);相反,在调用任何对象方法之前,它的值被放入寄存器(确切地说是EAX)。
不,Self在内存中有实际地址。尝试运行此代码以自行查看。
procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage(IntToStr(Integer(@Self)));
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  newform: TForm;
  p: ^Integer;
begin
  Self.Caption := 'TheOriginal';
  newform := TForm.Create(nil);
  try
    newform.Caption := 'TheNewOne';
    // The following two lines is, technically, the same as
    //   Self := newform;
    p := Pointer(@Self);
    p^ := Integer(newform);
    ShowMessage(Self.Caption);  // This will show 'TheNewOne' instead of 'TheOriginal'
  finally
    Self.Free; // Relax, this will free TheNewOne rather than TheOriginal
  end;
end;

改变堆栈中的值可能很危险 - 旧对象实例会发生什么?我猜会出现内存泄漏。 - mjn
1
@mjustin:更改堆栈值没有问题,因为它只是指向该对象的引用(指针),调用代码/所有者仍将保留对最终释放该对象的引用。 - mghie
这不是“按照惯例”的名称。它是内置于语言中的。您不能选择违反惯例在自己的程序中使用不同的名称。 - Rob Kennedy
“变量”Self“不在堆栈上(据我所知,它从未在堆栈上);相反,在调用任何对象方法之前,它的值被放入一个寄存器(确切地说是EAX)。 - PatrickvL
它应该被称为优化。是的,大多数时候,“Self”并没有存储在堆栈中。但我认为假设它在堆栈上是公平的,因为编译器总是会这样看待它。 - Sake
显示剩余2条评论

1
有时候,当你想要尽可能地优化一个方法(而不是使用汇编),'Self' 可以被滥用为一个 '自由' 变量 - 它可能只是使用堆栈和使用寄存器之间的差异。
当然,堆栈中的内容很可能已经存在于 CPU 缓存中,所以访问速度应该很快,但寄存器仍然更快。
顺便说一下:我仍然怀念在 Amiga 的 Motorola 68000 上编程的日子,那里有 16 个数据寄存器和 16 个地址寄存器的奢侈品... 我无法相信世界选择了 80x86 系列处理器的有限 4 个寄存器!
最后,我选择有时使用 Self,因为 Delphi 的优化器实际上并没有很好地进行优化。 (至少,它与例如各种 LLVM 优化器中可以找到的技巧相比显得苍白。)当然,这只是我的个人看法,你的情况可能会有所不同。

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