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

Ian*_*oyd 13 delphi const interface

我最近(再次)遇到Delphi编译器代码生成错误,当传递接口const泄漏引用时.

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

procedure Frob(const Grob: IGrobber);
Run Code Online (Sandbox Code Playgroud)

并修复是简单地删除const:

procedure Frob(Grob: IGrobber);
Run Code Online (Sandbox Code Playgroud)

我理解const(和var,和out)允许您通过引用传递项目.在结构的情况下,这保存了参数副本; 让你只需将指针传递给项目.

在的情况下Object/ Pointer/ Interface没有必要通过引用传递,因为它一个引用; 它已经适合注册.

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

const [A-Za-z]+\: I[A-Z]
Run Code Online (Sandbox Code Playgroud)

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

但有一些我无法改变.该TWebBrowser回调事件被声明为:

OnDocumentComplete(Sender: TObject; const pDisp: IDispatch; var URL: OleVariant);
                                    \___/
                                      |
                                      ?
Run Code Online (Sandbox Code Playgroud)

我走得太远了吗?我做了一件坏事吗?

编辑:或者,用一个较少"基于意见"的风格问题来表达它:是否有任何严重的缺点,不能将接口作为const传递?

额外奖励:当Delphi没有(总是)增加接口引用计数时,它们违反了COM规则:

参考计数规则

规则1: 必须为接口指针的每个新副本调用AddRef,并且每次销毁接口指针都会调用Release,除非后续规则明确允许.

规则2:关于接口指针的两个或多个副本的生命周期的开始和结束的关系的一段代码的特殊知识可以允许省略AddRef/Release对.

因此,虽然它可能是编译器可以利用的优化,但它必须正确地执行以便不违反规则.

Dav*_*nan 16

如果声明您的方法将接口变量作为const传递,例如:

procedure Frob(const Grob: IGrobber);
Run Code Online (Sandbox Code Playgroud)

那不太对劲.为了存在泄漏,您需要在代码中没有任何内容可以引用新创建的对象.所以,如果你写:

Frob(grob);
Run Code Online (Sandbox Code Playgroud)

那里没有问题,因为界面grob已经至少有一个参考.

你写的时候会出现问题:

Frob(TGrobberImplementer.Create);
Run Code Online (Sandbox Code Playgroud)

在那种情况下,没有任何参考接口,所以它被泄露.好吧,只要实现中的任何内容都没有Frob引用它,它就会被泄露.

我做了一件坏事吗?

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

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

procedure OnDocumentComplete(Sender: TObject; const pDisp: IDispatch; var URL: OleVariant);
Run Code Online (Sandbox Code Playgroud)

举个例子.那里没有问题,因为你从不称呼那种方法.它是您实现的事件处理程序.框架调用它,并传递已经引用的接口.

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

这很容易解决,尽管很烦人.

grob := TGrobberImplementer.Create;
Frob(grob);
Run Code Online (Sandbox Code Playgroud)

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

  1. 按值传递接口参数会产生性能成本.
  2. 我无法确保我调用的每个方法都会按值接受接口参数.
  3. 因此,我接受这样一个事实,即我需要const至少在某些时候处理调用接口参数.
  4. 因为我必须在某些时候处理它,因为我讨厌不一致,所以我总是选择接受处理它.
  5. 因此,我选择在我编写的方法中创建所有接口参数const.
  6. 因此,我确保除非已经由变量引用,否则我永远不会将接口作为参数传递.

  • FWIW,而不是你的答案末尾的双线和额外变量,你可以在没有额外变量的情况下在一行中完成,但只能使用强制转换:`Frob(TGrobberImplementer.Create as IGrobber);`.这也会将引用计数增加到1,就像你的双线程一样. (5认同)
  • 我想知道为什么dcc不能为接口表达式创建一个隐藏变量(比如`Frob(TGrobberImplementer.Create);`)就像它对字符串表达式一样... (3认同)
  • @RudyVelthuis我没有想到这一点,但它会发生,因为编译器感到有必要防止`as`失败,它仍然需要处理原始接口.因此它引入了一个隐式局部变量.我发现很难相信很难改变编译器来修复这个长期存在的错误. (2认同)