Delphi 为什么使用过程创建对象比使用函数更受欢迎?

Delphi 为什么使用过程创建对象比使用函数更受欢迎?,delphi,Delphi,这类似于。我问他“为什么?”但我不知道有人会再看一眼。至少不是及时的 无论如何,我的问题是关于将创建对象的责任委托给函数或过程的最佳实践,而不会导致内存泄漏。这似乎是: procedure FillObject(MyObject: TMyObject; SomeParam: Integer); begin //Database operations to fill object end; procedure CallUsingProcedure(); var MyObject: TMy

这类似于。我问他“为什么?”但我不知道有人会再看一眼。至少不是及时的

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

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++也比较多,不过程度较低。直觉上,我倾向于函数方法,因为:

  • 它将对象创建代码封装在函数中,而不是在需要使用过程时单独创建对象
  • 我不喜欢改变参数的方法。它通常没有文档记录,并且会使跟踪bug变得更加困难
  • 含糊不清,但不可否认,这对我来说只是“气味”不好
我不是说我是对的。我只是想了解为什么社区选择这种方法,以及是否有充分的理由让我改变

编辑:
评论中对@E-Rock的引用是对我而言的(Eric G)。我更改了显示名称。

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

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

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

MyObj := CreateMyObject(DBID);
什么代码应该释放它?你什么时候能安全地释放它?谁负责异常处理?你如何知道(作为其他人代码的用户)


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

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

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

procedure PopulateStrings(Strings: TStrings);
对于该过程,您可以传递任何类型的tstring,无论是TMemo的行、TListBox或TComboBox的项还是简单的独立TStringList。如果您有一个功能:

function CreateStrings: TStrings;
您总是会得到相同类型的对象(确切地说,哪个对象是未知的,因为tstring是抽象的,所以您可能会得到一个TStringList),并且必须将()内容分配给要修改的tstring。IMO首选程序

此外,如果您是函数的作者,则无法控制创建的对象是否被释放或何时释放。如果您编写了一个过程,那么这个问题就可以解决了,因为用户提供了对象,而对象的生命周期与您无关。您不必知道对象的确切类型,它必须是类或参数的后代。瞧,这对函数的作者来说也更好

出于给出的所有原因,从函数返回对象通常不是一个好主意。仅修改对象的过程对对象没有依赖性,也不会为用户创建依赖性

FWIW,另一个问题是,如果从DLL执行此操作。返回的对象使用DLL的内存管理器,以及它在DLL中指向的VMT。这意味着在用户代码中使用
as
is
的代码不能正常工作(因为
is
as
使用VMT指针检查类标识)。如果用户必须将其对象传递给过程,则不会出现该问题

更新
正如其他人评论的那样,将对象传递给DLL也不是一个好主意。非虚拟函数将调用DLL中的函数并使用其内存管理器,这也会导致问题。而
as
也不会在DLL中正常工作所以不要将对象传入或传出DLL。这与DLL应该只使用参数(或只包含POD类型的复合类型——数组、记录)或COM接口的最大原则是一致的。COM接口也应该只使用相同类型的参数。

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

这种技术具有(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);

虽然我基本上同意这一点,但它显然被称为
CreateMyObject
,这意味着。。。创建一个新对象并将所有权转移给调用方(我)。在创建过程中可能需要做一些事情,将这种方法转化为某种“工厂方法”。你是对的。对于我的“坏”例子,我选择了一个糟糕的名字作为例子。编辑-谢谢你指出。哇。人们似乎更关注我选择的名字,而不是文本的内容编辑为更接近原始问题的命名/使用@E-Rock:它们都很重要,但对我来说,首先要明确内存/资源管理的责任,因为它们可能会对整个系统产生影响。不,E-Rock。你的问题
sl3:=TMyStringList.Create;
....
MakeList(sl3);