Delphi函数返回类对象

Raf*_*ssi 4 delphi

除了这个问题,我还对docwiki进行了一些测试和研究.我的结论是这种代码应该没有内存泄漏:

function testResultObject: TClassA;
begin
 Result := TClassA.Create;
 Result.DoSomething;
end;
Run Code Online (Sandbox Code Playgroud)

然后我可以用这种方式调用上面的代码:

var k: TClassA;
begin

 k := testResultObject;
 try
  //code code code
 finally
  k.Free;
 end;

end;
Run Code Online (Sandbox Code Playgroud)

正如雷米在答案中建议的那样,最好避免这种做事方式,而是使用类似的东西testResultObject(x: TClassA): boolean.在这种情况下,返回true/false可以告诉我一切是否正常并且我正在传递已经创建的对象.

看看这段代码:

function testResultObject: TClassA;
begin

 Result := TClassA.Create;

 try
  Result.DoSomething;
 except
  Result.Free;
 end;

end;
Run Code Online (Sandbox Code Playgroud)

上面第一个版本的函数的问题是DoSomething可能引发异常,如果是这样,我会泄漏内存.第二个实现try-except可以解决吗?以后我必须检查结果是否已分配或为零.

我同意(如上所述)testResultObject(x: TClassA): boolean会更好.我只是想知道返回类功能的方式是否可以像我写的那样修复.

Dav*_*nan 9

您的代码存在严重问题.如果出现错误,它会吞下异常,并返回无效的对象引用.

这很容易解决.规范方式如下:

function testResultObject: TClassA;
begin
  Result := TClassA.Create;    
  try
    Result.DoSomething;
  except
    Result.Free;
    raise;
  end;
end;
Run Code Online (Sandbox Code Playgroud)

函数成功并返回一个新对象.或者它失败了,自行清理,并引发异常.

换句话说,此函数的外观和行为就像构造函数一样.您以相同的方式使用它:

obj := testResultObject;
try
  // do things with obj
finally
  obj.Free;
end;
Run Code Online (Sandbox Code Playgroud)

  • 我认为这是最好的答案.我喜欢用标准方式做事情,在这里我可以使用正常的try-finally.此功能正在进行清理,代码非常易于阅读. (2认同)

Dis*_*ned 5

你的第二种方法有效,但有两个严重的问题.

  • 通过吞下所有异常,(正如J指出的那样)你会隐瞒事情出错的事实.
  • 调用者没有迹象表明您创建了一个调用者负责销毁的对象.这使得使用该功能更容易出错; 并且更容易导致内存泄漏.

我建议您对第二种方法进行以下改进:

         {Name has a clue that caller should take ownership of a new object returned}
function CreateObjectA: TClassA;
begin
  {Once object is successfully created, internal resource protection is required:
      - if no error, it is callers responsibility to destroy the returned object
      - if error, caller must assume creation *failed* so must destroy object here
  Also, by assigning Result of successful Create before *try*:
      The object (reference) is returned
          **if-and-only-if**
      This function returns 'normally' (i.e. no exception state)}
  Result := TClassA.Create;    
  try
    Result.DoSomething; {that could fail}
  except
    {Cleanup only if something goes wrong:
      caller should not be responsible for errors *within* this method}
    Result.Free;
    {Re-raise the exception to notify caller:
      exception state means caller does not "receive" Result...
      code jumps to next finally or except block}
    raise;
  end;
end;
Run Code Online (Sandbox Code Playgroud)

上述create函数最重要的好处是:就任何调用者/客户端代码而言,它的行为与普通的TObject.Create完全相同.
因此正确的使用模式完全相同.

请注意,我并不热衷于J的FreeAndNil建议,因为如果调用代码不检查结果是否已分配:它很可能是AV.并且正确检查结果的代码会有点混乱:

var k: TClassA;
begin
  k := testResultObject; {assuming nil result on failed create, next/similar is *required*}
  if Assigned(k) then    {Note how this differs from normal try finally pattern}
  try
    //code using k
  finally
    k.Free;
  end;
end;
Run Code Online (Sandbox Code Playgroud)

注意:重要的是要注意,你不能让你的来电者忽略内存管理; 这让我进入下一节.


除此之外,如果你testResultObject需要一个输入对象,你需要调用者根据需要创建和管理它的生命周期,那么制造粗心错误的可能性要小得多.我不确定你为什么这么拒绝这种方法?如果不采用不同的内存模型,就不能比以下更简单.

var k: TClassA;
begin
  k := TClassA.Create;
  try
    testResultObject(k); {Where this is simply implemented as k.DoSomething;}
    //more code using k
  finally
    k.Free;
  end;
end;
Run Code Online (Sandbox Code Playgroud)