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

Eri*_*c G 16 delphi

这与此问题类似.我问"为什么?" 以最流行的反应,但我不知道任何人会永远看着它一次.至少不是及时的.

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

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;
Run Code Online (Sandbox Code Playgroud)

比这更受欢迎:

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;
Run Code Online (Sandbox Code Playgroud)

为什么?

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

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

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

编辑: 评论中对@ E-Rock的引用是给我的(Eric G).我改变了显示名称.

Rud*_*uis 15

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

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

procedure PopulateStrings(Strings: TStrings);
Run Code Online (Sandbox Code Playgroud)

程序,你可以通过任何类型的字符串列表,无论是线 A的TMemo,该项目一的一个TListBoxTComboBox或简单的独立TStringList中.如果你有一个功能:

function CreateStrings: TStrings;
Run Code Online (Sandbox Code Playgroud)

你总是得到相同类型的对象(这个对象完全不知道,因为TStrings抽象的,所以你可能得到一个TStringList),并且必须内容Assign()分配给你想要修改的TStrings.该程序是首选,IMO.

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

出于所有原因,IMO很少从一个函数返回一个对象.甲过程仅修改该对象具有在对象上没有依赖性和为用户创建没有依赖性.

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

更新

正如其他人评论的那样,将对象传递给DLL也不是一个好主意.非虚函数将调用DLL中的函数并使用其内存管理器,这也可能导致麻烦.而isas不正确的DLL里面工作的.因此,不要将对象传入或传出DLL.这与DLLs应该只使用POD类型参数(或复合类型 - 数组,记录 - 仅包含POD类型)或COM接口的maxime相关.COM接口也应该只使用相同类型的参数.

  • 将对象*传递给*DLL也同样糟糕.如果DLL调用该对象上的任何非虚函数,则代码将转到DLL的函数版本,因此您最终会使用错误的内存管理器或甚至错误的RTL版本,并且`is`和`as`将继续失败. (3认同)

Ken*_*ite 13

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

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

MyObj := TMyObject.Create;
try
  // Do whatever with MyObj
finally
  MyObj.Free;
end;
Run Code Online (Sandbox Code Playgroud)

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

MyObj := CreateMyObject(DBID);
Run Code Online (Sandbox Code Playgroud)

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

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

  • 不,E-Rock.你的问题预设了一些尚未证实的事实.当目标是构造一个新对象时,我更喜欢工厂函数技术.如果目标是使用现有对象,那么我将使用过程技术.他们都有自己的用途.工厂方法可能确实不太常见.这肯定不太常见*正确*(即,通过适当的异常处理). (5认同)
  • 虽然我大多同意这一点,但它显然叫做"CreateMyObject",这意味着......创建了一个新对象,并将所有权转移给了调用者(对我来说).在创建过程中可能需要做一些事情,将这种方法转变为各种"工厂方法". (2认同)
  • 哇.人们似乎更专注于我选择的名字而不是文本的内容.:)编辑更接近原始问题的命名/使用.@ E-Rock:它们都很重要,但对我来说,内存/资源管理的责任是明确的,因为它们可以产生系统范围的影响. (2认同)

PA.*_*PA. 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;
Run Code Online (Sandbox Code Playgroud)

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

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

var sl1,sl2,sl3:TStrings;
sl1:=MakeList;
Run Code Online (Sandbox Code Playgroud)

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

sl2:=MakeList(TMyStringsList.create);
Run Code Online (Sandbox Code Playgroud)

何时创建对象

sl3:=TMyStringList.Create;
....
MakeList(sl3);
Run Code Online (Sandbox Code Playgroud)