4

我有两个单元 unitA 和 unitB。TFoo 类在 unitB 中声明。

在 unitA 的最终确定中调用 B.Free 是否总是安全的?

它如何取决于 unitA 和 unitB 在 dpr 中的顺序?

执行 unitA 最终确定时,我可以确定 unitB 存在吗?

unit unitB;
interface
type
 TFoo = class
   // code...
  end;
 // code....
end;

unit unitA;
// code..
implementation
uses
 unitB;
var
 A: TStringList;
 B: UnitB.TFoo;

initialization
 A:= TStringList.Create;
 B:= UnitB.TFoo.Create;
finalization
 A.Free;
 B.Free;  // Is it safe to call?
end.
4

6 回答 6

16

是的,你应该没问题,因为 B 是在单元 A 中创建的。规则是初始化部分是根据它们在 DPR 中的顺序调用的,除非其中一个单元引用另一个单元。在这种情况下,引用的单元首先被初始化。最终确定是相反的顺序。

在您的情况下,单元 B 没有初始化部分,因此这是一个有争议的问题。然而,当执行 Unit A 初始化部分时,它将使用 Unit B 中的 TFoo 定义。

关于初始化和终结部分的另一个警告词——它们发生在全局异常处理程序之外。那里发生的任何异常都会终止应用程序。因此,在大型程序中跟踪和调试这些异常可能会很痛苦。您可以考虑在其中使用您自己的异常日志记录,以防万一。

于 2010-02-20T07:26:07.957 回答
4

不。您可以尝试,您可以希望,但不能保证调用初始化和完成的顺序。参阅qc72245qc56034等。

更新:

  1. 终结部分的执行顺序与初始化相反。您的示例是安全的,您不需要在单元之间调用初始化部分
  2. Delphi 可以混合调用单元(第 1 点仍然有效,初始化和终结部分都交换了)

例子:

unitA // no dependency on unitB
var SomeController;
initialization
  SomeController := TSomeController.Create;
finalization
  SomeController.Free;

unitB
uses
  unitA;
initialization
  SomeController.AddComponent(UnitBClass);
finalization
  SomeController.RemoveComponent(UnitBClass);

跟注的常见(正确)顺序(99.99%):

  1. unitA.初始化
  2. unitB.初始化
  3. 跑...
  4. unitB.finalization
  5. unitA.finalization

有时Delphi编译文件会出错:

  1. unitB.initialization - 这里是 AV
  2. unitA.初始化
  3. 跑...
  4. unitA.finalization
  5. unitB.finalization - 这里也是

题外话小故事:

我们有一个相当大的项目,Unit1 中的 Type1,Unit2 中的 Type2 = class(Type1)。文件在 project.dpr 中排序,多年后添加 Unit200(与 unit1/2 没有依赖关系) Delphi 在 Unit1.Initialization 之前使用 Unit2.Initialization 开始编译项目。唯一安全的解决方案是从初始化部分调用您自己的 Init 函数。

于 2010-02-20T10:42:26.717 回答
3

据我了解,您所拥有的应该是完全有效的。有点尴尬但有效。

但更好的方法可能是在单元 B 中声明一个变量并让 B 初始化/完成它。由于初始化发生在调用任何其他代码之前,只要它在单元 A 的使用子句中声明,它将在单元 A 可用之前被初始化。

您可能要考虑的另一步骤是将 B 的单位变量更进一步,并将其作为按需加载的函数调用,但这也可能取决于您的使用情况。

例如

unit unitB;
interface
type
 TFoo = class
   // code...
  end;
 // code....
 function UnitVarB:TFoo;

implementation

var
  gUnitVarB : TFoo;  

function UnitVarB:TFoo 
begin
  if not assigned(gUnitVarB) then
    gUnitVarB := TFoo.Create;

  result := gUnitVarB;
end;

finalization
  if assigned(gUnitVarB) then
    gUnitVarB.free;  //or FreeAndNil(gUnitVarB);

end;

unit unitA;

// code..
implementation

uses
 unitB;

var
 A: TStringList;

//code...
  ...UnitVarB....
//code...

initialization
 A:= TStringList.Create;
finalization
 A.Free;
end.

我似乎记得某处单元初始化可能很昂贵,因为如果您不再直接引用的单元在编译期间仍在您的 uses 子句中,则智能链接器不会因为初始化部分而将其删除。虽然如果每个单元都有一个初始化部分,这听起来可能不是那么糟糕,但大多数 Delphi 程序将比它们已经大得多

我并不是说不要使用它们,但我的经验法则是谨慎使用它们。

您的初始代码示例违反了该规则。我想我会提到它。

瑞安

于 2010-02-20T07:12:43.443 回答
3

在您在这里显示的特定情况下,您会没事的。但是在它开始出错之前不需要那么多的重构。

Delphi 在确保单元在需要时保留在内存中做得很好。但只有当它知道需要一个单元时,它才能这样做。

我关于该主题的经典示例是一个单元,它只包含一个对象列表

unit Unit1;

interface
uses
  Contnrs;
var
  FList : TObjectList;
implementation

initialization
  FList := TObjectList.Create(True);
finalization
  FList.Free;
end.

Unit1 仅显式依赖于 Contnrs。Delphi 只会确保 Contnrs 单元(可能还有“从属”单元,尽管我不是 100% 确定)仍然加载到内存中。如果在列表中添加了 TForm,则 Forms 单元在被调用时可能已经完成,FList.free当它尝试释放它包含的 TForm 时会崩溃。Delphi 无法知道 Unit1 需要 Forms 单元。在这种特定情况下,它将取决于在 dpr 中声明的顺序单位。

于 2014-11-21T21:25:19.857 回答
1

是的,那是安全的。您可以通过在 dpr 文件中在 UnitA 之前声明 UnitB 来简化编译器的工作,但无论如何编译器都会解析引用。

于 2010-02-20T07:21:28.340 回答
1

本着充分披露的精神,我自 2005 年以来就没有在 Delphi 中开发过。但是,我在 1996 年从 Delphi 1 开始专门在 Delphi 中开发,并在 2001 年在 Delphi 5 中获得认证。话虽如此,我使用的 finalization 部分很少见。我唯一会使用它是如果我需要在 .dpr 中设置一些特殊的东西。这通常仅在我进行自定义组件开发时才会发生,并且我需要使用正在开发的其他自定义组件来管理一些依赖项。

对于典型的应用程序开发,我远离初始化/完成部分,只使用单例、外观和工厂等设计模式来管理我的类的创建和管理。内置的垃圾收集器足以满足我 98.5% 的项目。

要回答您的问题,您需要在 UnitA 代码中设置对 TFoo 的依赖关系,并且正如 Ryan 建议的那样,确保在销毁之前对其进行分配。话虽如此,我鼓励您在投入太多时间之前确保使用初始化/完成部分是必要的。

于 2010-02-20T07:25:36.553 回答