11.1.3 混合引用中的错误
在使用对象时,你通常应该只使用对象变量或接口变量来访问它们。混合使用这两种方法会破坏对象 Pascal 所提供的引用计数机制,并可能导致极难跟踪的内存错误。在实践中,如果你决定使用接口,你可能应该只使用基于接口的变量。
下面是许多可能的类似情况中的一个例子。假设你有一个接口、一个实现接口的类和一个将接口作为参数的全局过程:
type
IMyInterface = interface
['{F7BEADFD-ED10-4048-BB0C-5B232CF3F272}']
procedure Show;
end;
TMyIntfObject = class(TInterfacedObject, IMyInterface)
public
procedure Show;
end;
procedure ShowThat(AnIntf: IMyInterface);
begin
AnIntf.Show;
end;
这段代码看起来相当琐碎,而且百分之百正确,但可能出错的地方是你调用过程的方式(该代码是 IntfError 示例的一部分):
procedure TForm1.BtnMixClick(Sender: TObject);
var
AnObj: TMyIntfObject;
begin
AnObj := TMyIntfObject.Create;
try
ShowThat(AnObj);
finally
AnObj.Free;
end;
end;
在这段代码中发生的情况是,我将一个普通对象传递给了一个期待接口的函数。鉴于对象确实支持接口,编译器在调用时没有问题。问题出在内存管理方式上。
起初,对象的引用计数为零,因为没有接口指向它。进入 ShowThat 过程后,引用计数增加到 1。现在,在退出存储过程时,引用计数减少并归零,因此对象被销毁。换句话说,当你将 AnObj 传递给过程时,它就会被销毁,这确实有点尴尬。如果运行这段代码,就会出现内存错误。
这种问题可以有多种解决方案。你可以人为地增加引用计数并使用其他低级技巧。但真正的解决办法是不要将接口和对象引用混用,而只使用接口来引用对象(此代码再次取自 IntfError 示例):
procedure TForm1.BtnIntfOnlyClick(Sender: TObject);
var
AnIntf: IMyInterface;
begin
AnIntf := TMyIntfObject.Create;
ShowThat(AnIntf);
end;
在这种特定情况下,解决方案非常简单,但在许多其他情况下,却很难编写出正确的代码。同样,经验法则是避免混合使用不同类型的引用。不过,请继续阅读下一节,了解一些最新的替代方法。