我不应该将接口作为常量传递吗?

Should I not pass an interface as const?

我最近(又)遇到了 the Delphi compiler code-gen bug when passing an interface as const 泄漏了一个参考。

如果您的方法被声明为将接口变量作为 const 传递,则会发生这种情况,例如:

procedure Frob(const Grob: IGrobber);

修复方法是简单地删除 const:

procedure Frob(Grob: IGrobber);

我了解到 const(以及 varout)允许您通过引用传递项目。在结构的情况下,这会保存一个参数副本;让您只需将指针传递给该项目即可。

Object/Pointer/Interface 的情况下,不需要通过引用传递,因为它 参考;它已经适合寄存器。

为了再也不会遇到这个问题,我进行了一次十字军东征。我搜索了我所有的源代码树:

const [A-Za-z]+\: I[A-Z]

我删除了大约 150 个实例,其中我将接口作为 const 传递。

但有些我无法更改。 TWebBrowser 回调事件声明为:

OnDocumentComplete(Sender: TObject; const pDisp: IDispatch; var URL: OleVariant);
                                    \___/
                                      |
                                      ?

我是不是走得太远了?我做了坏事吗?

编辑:或者,用更少 "based on opinion" 风格的问题来表达它:是否有任何严重的缺点 将接口作为常量传递?

Bonus:当 Delphi 不(总是)增加接口引用计数时,它们违反了 The Rules of COM:

Reference-Counting Rules

Rule 1: AddRef must be called for every new copy of an interface pointer, and Release called for every destruction of an interface pointer, except where subsequent rules explicitly permit otherwise.

Rule 2: Special knowledge on the part of a piece of code of the relationships of the beginnings and the endings of the lifetimes of two or more copies of an interface pointer can allow AddRef/Release pairs to be omitted.

因此,虽然这可能是编译器可以利用的一种优化,但它必须正确执行,以免违反规则。

This happens if your method is declared to pass an interface variable as const, e.g.:

procedure Frob(const Grob: IGrobber);

这不太正确。为了防止泄漏,您需要在代码中没有任何内容引用新创建的对象。所以如果你写:

Frob(grob);

没有问题,因为接口 grob 已经至少有一个引用。

写的时候出现问题:

Frob(TGrobberImplementer.Create);

在那种情况下,没有任何东西引用接口,所以它被泄露了。好吧,只要 Frob 的实现中没有引用它,它就会被泄露。

Have I done a bad thing?

好吧,这取决于。我认为你所做的事情不会有什么特别糟糕的结果。在性能方面存在一个缺点,因为所有接受接口参数的函数现在都必须使用隐式 try/finally 块添加和释放引用。只有你能判断这是否重要。

更重要的问题与您无法控制的代码有关。你给

procedure OnDocumentComplete(Sender: TObject; const pDisp: IDispatch; var URL: OleVariant);

举个例子。那里没有问题,因为您永远不会调用该方法。它是您实现的事件处理程序。框架调用它,它传递一个已经被引用的接口。

真正的问题来自 RTL 中声明的方法或您调用的任何其他第三方代码。如果您正在调用这些方法,并且如果它们使用 const 接口参数,那么您可能会落入陷阱。

解决起来很容易,尽管很烦人。

grob := TGrobberImplementer.Create;
Frob(grob);

我处理这个问题的理由是这样的:

  1. 按值传递接口参数会产生性能成本。
  2. 我不能确保我调用的每个方法都会按值接受接口参数。
  3. 因此,我接受这样一个事实,即至少在某些时候我需要处理调用 const 接口参数。
  4. 因为我有时不得不处理它,而且因为我讨厌前后矛盾,所以我选择一直接受处理它。
  5. 因此,我选择将我编写的方法中的所有接口参数设为const
  6. 因此我确保我永远不会将接口作为参数传递,除非它已经被变量引用。