我不应该将接口作为常量传递吗?
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
(以及 var
和 out
)允许您通过引用传递项目。在结构的情况下,这会保存一个参数副本;让您只需将指针传递给该项目即可。
在 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);
我处理这个问题的理由是这样的:
- 按值传递接口参数会产生性能成本。
- 我不能确保我调用的每个方法都会按值接受接口参数。
- 因此,我接受这样一个事实,即至少在某些时候我需要处理调用
const
接口参数。
- 因为我有时不得不处理它,而且因为我讨厌前后矛盾,所以我选择一直接受处理它。
- 因此,我选择将我编写的方法中的所有接口参数设为
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
(以及 var
和 out
)允许您通过引用传递项目。在结构的情况下,这会保存一个参数副本;让您只需将指针传递给该项目即可。
在 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);
我处理这个问题的理由是这样的:
- 按值传递接口参数会产生性能成本。
- 我不能确保我调用的每个方法都会按值接受接口参数。
- 因此,我接受这样一个事实,即至少在某些时候我需要处理调用
const
接口参数。 - 因为我有时不得不处理它,而且因为我讨厌前后矛盾,所以我选择一直接受处理它。
- 因此,我选择将我编写的方法中的所有接口参数设为
const
。 - 因此我确保我永远不会将接口作为参数传递,除非它已经被变量引用。