为什么使用过程来创建优先于函数的对象?

时间:2011-09-14 22:11:03

标签: delphi

这类似于this question。我问“为什么?”到the most popular response,但我不知道有人会再看一遍。至少不是及时的。

无论如何,我的问题是关于将对象的创建责任委派给函数或过程的最佳实践,而不会导致内存泄漏。看来这个:

procedure FillObject(MyObject: TMyObject; SomeParam: Integer);
begin
  //Database operations to fill object
end;

procedure CallUsingProcedure();
var
  MyObject: TMyObject;
begin
  MyObject = TMyObject.Create();
  try
    FillObject(MyObject, 1);
    //use object
  finally
    MyObject.Free();
  end;
end;

优先于此:

function CreateMyObject(DBID: Integer): TMyObject;
begin
  Result := TMyObject.Create();
  try
    //Database operations to fill object
  except on E: Exception do
    begin
      Result.Free();
      raise;
    end;
  end;
end;

procedure CallUsingFunction();
var
  MyObject: TMyObject;
begin
  MyObject = CreateMyObject(1);
  try
    //use object
  finally
    MyObject.Free();
  end;
end;

为什么?

我对Delphi比较陌生,以前在Java和PHP以及C ++方面工作最多,但程度较小。直觉上,我倾向于函数方法,因为:

  • 它将对象创建代码封装在函数中,而不是在我想要使用该过程时单独创建对象。
  • 我不喜欢改变参数的方法。它通常没有文档记录,可以使跟踪错误更加困难。
  • 含糊不清,但不可否认,这对我来说“闻起来不好”。

我不是说我是对的。我只是想了解为什么社区选择这种方法,以及是否有充分的理由让我改变。

修改 在评论中提及@ E-Rock的是我(Eric G)。我改变了显示名称。

3 个答案:

答案 0 :(得分:15)

Ken White写的一个问题是:你将函数的用户交给他或她必须释放的对象。

过程的另一个优点是,您可以传递层次结构中的多个对象,而创建此类对象的函数始终生成相同的对象。 E.g。

procedure PopulateStrings(Strings: TStrings);

对于程序,您可以传递任何类型的 TStrings ,无论是 TMemo的 TListBox TComboBox Items 或简单的独立 TStringList 。如果你有一个功能:

function CreateStrings: TStrings;

你总是得到同样的对象(这个对象完全不知道,因为 TStrings 抽象,所以你可能得到一个 TStringList ),并且将内容分配()到要修改的 TStrings 程序首选,IMO。

此外,如果您是该函数的作者,则无法控制是否释放您创建的对象,或何时释放。如果你编写了一个程序,那么这个问题就会被取消,因为用户提供了这个对象,它的生命周期并不是你所关心的。而且您不必知道对象的确切类型,它必须只是参数的类或后代。 IOW,对函数的作者来说也好得多。

出于所有原因,IMO很少从一个函数返回一个对象。仅修改对象的过程不依赖于对象,并且不会为用户创建依赖项。

FWIW,另一个问题是如果你从DLL那样做。返回的对象使用DLL的内存管理器,以及它指向的VMT在DLL中。这意味着在用户代码中使用asis的代码无法正常工作(因为isas使用VMT指针来检查类标识)。如果用户必须将他的对象传递给过程,则不会出现该问题。

更新

正如其他人评论的那样,将对象传递给DLL也不是一个好主意。非虚函数将调用DLL中的函数并使用其内存管理器,这也可能导致麻烦。并且isas也无法在DLL中正常运行。 所以只是不要将对象传入或传出DLL 。这与DLL应该只使用POD type参数(或复合类型 - 数组,记录 - 仅包含POD类型)或COM接口的maxime相关。 COM接口也应该只使用相同类型的参数。

答案 1 :(得分:13)

创建对象实例并将其传递到另一个过程可以清楚地说明哪个代码负责释放实例。

在第一种情况下(使用程序填充):

MyObj := TMyObject.Create;
try
  // Do whatever with MyObj
finally
  MyObj.Free;
end;

很明显,这段代码负责在使用完成后释放MyObj

MyObj := CreateMyObject(DBID);

应该释放什么代码?什么时候可以安全地释放它?谁负责异常处理?你怎么知道(作为别人代码的用户)?

作为一般规则,您应该在需要的地方创建,使用和释放对象实例。这使您的代码更易于维护,并且肯定会使以后出现的人更容易并且必须尝试解决它。 :)

答案 2 :(得分:6)

我使用两种成语的组合。将对象作为可选参数传递,如果未传递,则创建对象。在任何一种情况下都将对象作为函数结果返回。

该技术具有(1)在被调用函数内部创建对象的灵活性,以及​​(2)将对象作为参数传递的调用者的调用者控制。控制有两个含义:控制所用对象的实际类型,并控制何时释放对象。

这段简单的代码就是这个习惯用语的例证。

function MakeList(aList:TStrings = nil):TStrings;
 var s:TStrings;
 begin
   s:=aList;
   if s=nil then 
     s:=TSTringList.Create;
   s.Add('Adam');
   s.Add('Eva');
   result:=s;
 end;

以下是三种不同的使用方法

最简单的用法,用于快速和脏代码

var sl1,sl2,sl3:TStrings;
sl1:=MakeList;

当程序员想要更明确的所有权和/或使用自定义类型时

sl2:=MakeList(TMyStringsList.create);

以前创建对象时

sl3:=TMyStringList.Create;
....
MakeList(sl3);