9

从delphi中的函数/过程返回简单对象的最佳实践是什么?

例如。2种代码:

将创建的对象作为引用传递,在 Proc 中填充对象,然后将其销毁

procedure Proc(var Obj: TMyObject);
begin
  // populate Obj
end;

O := TMyObject.Create;
try
  Proc(O);
  // manipulate populated object
finally
  O.Free;
end;

从函数中获取创建的对象,在操作后销毁

function Func: TMyObj;
begin
  Result := TMyObj.Create;
end;

O := Func;
if O <> nil then
begin
  try
    // manipulate
  finally
    O.Free;
  end;
end;
4

6 回答 6

8

没有最佳实践。但是,您应该做的主要事情是确保始终清楚谁负责在任何给定时间销毁对象,即使发生异常也是如此。

创建一个新实例并返回它的函数没有任何问题。这样的功能就是工厂。您可以将其视为类的构造函数,因此应确保它的行为类似于构造函数:返回有效对象或引发异常。它从不返回空引用。

function Func: TMyObj;
begin
  Result := TMyObj.Create;
  try
    Result.X := Y;
  except
    Result.Free;
    raise;
  end;
end;

这是一种您不经常看到的异常处理模式,但它对于这种函数风格很重要。返回对象会将所有权从函数转移给调用者,但前提是它设法完全执行。如果由于异常而不得不提前离开,它会释放对象,因为调用者无法自行释放它。(由于异常而终止的函数没有返回值。)调用者将像这样使用它:

O := Func;
try
  writeln(O.X);
finally
  O.Free;
end;

如果出现异常,FuncO永远不会被分配,因此调用者没有任何可用的空闲空间。


当调用者创建对象并将其传递给另一个函数以对其进行初始化时,请勿将参数设为“var”参数。这对调用者施加了某些限制,调用者必须使用与函数请求的类型完全相同的变量,即使创建了一些后代类型也是如此。

这样的函数不应该释放对象。调用者不会将所有权责任授予它调用的函数,尤其是当它计划在函数返回后使用该对象时。

于 2009-11-08T20:14:21.277 回答
4

这取决于对象的生命周期以及谁负责它。大多数时候对象应该由同一个实体创建和销毁。

假设您的方法使用解析文件的结果填充 TStringList。您应该让该函数创建 TStringList,还是应该创建它并作为参考传递?

我发现在连续的代码行中创建它、将它作为引用传递、然后销毁它更具可读性。

现在让我们考虑您有一个函数,它为每个添加的客户返回一个 TCustomer。在那种情况下,我会使用一个函数,因为我认为我的实体会有一个客户列表或其他东西,负责在不需要时销毁它们。

于 2009-11-08T18:19:13.687 回答
3

让调用者创建对象并将其作为参数传递是一种常见的 Delphi 习惯用法。请注意,您不必var在几乎所有情况下都声明它。

procedure Proc (Obj : TMyObject)
begin
  Obj.SomeProperty := 'SomeValue';
  ...
end;

调用代码:

Obj := TMyObject.Create;
try
  Proc (Obj);
finally
  FreeAndNil (Obj);
end;    

这避免了关于谁必须释放对象的混淆。请注意,如果您有一系列方法调用,那么跟踪需要在沿线某处释放的对象可能会变得非常复杂。

还有一个缺点:创建和销毁分散在代码中使得无法使用try...finally块,这只是避免资源泄漏的另一种有用的习惯用法。

如果您希望您的方法创建对象,我会在函数名称中明确说明,这CreateAndInitializeList听起来对我来说是正确的。

于 2009-11-08T19:05:59.387 回答
2

我的规则是拥有所有权和创造权。我总是让创建者成为所有者,因此有责任销毁对象。对象的创建在调用代码中是明确的,它绝不是调用的副作用。

所以我的函数的通常签名是

function Func(o:tMyO): TMyO;
begin
  // ....
  Result := o;
end;

这样我可以做

   o := func(TMyO.create);

或者

  o := TMyO.create;
  // ...
  func(o);
于 2009-11-08T18:54:59.793 回答
1

如前所述,通常创建对象的同一实体应该释放它,这意味着调用者应该创建对象引用,而不是让它在函数内部完成。

但是,这只有在调用者知道要返回的项目的确切类型而不是超类型时才有可能。例如:

var E: TEmployee;

E := CreateEmployee(EmployeeID);  // Could return TEmployee or subclasses TManager or TRetiredEmployee

try

    E.SendEmail(MessageText);
    if (E is TRetiredEmployee) then
        E.PrintLetter;

finally

    E.Free;

end;

在这种情况下,我发现在我正在调用的工厂函数的名称中包含“创建”一词或其他指示符会很有帮助。

于 2009-11-08T20:28:29.323 回答
-3

我经常使用构造

FUNCTION SomeFunction(SL : TStrings = NIL) : TStrings;
  BEGIN
    IF Assigned(SL) THEN Result:=SL ELSE Result:=TStringList.Create;
    // Use Result for the remainder of the function
  END;

这样,我既可以将它用作带有传入引用的 PROCEDURE,也可以用作创建实例本身的 FUNCTION。

于 2009-11-08T18:40:56.193 回答