代码之家  ›  专栏  ›  技术社区  ›  Raffaele Rossi

Delphi函数返回类对象

  •  4
  • Raffaele Rossi  · 技术社区  · 7 年前

    除了 this

    function testResultObject: TClassA;
    begin
     Result := TClassA.Create;
     Result.DoSomething;
    end;
    

    然后在某个地方我可以这样调用上面的代码:

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

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

    function testResultObject: TClassA;
    begin
    
     Result := TClassA.Create;
    
     try
      Result.DoSomething;
     except
      Result.Free;
     end;
    
    end;
    

    DoSomething try-except 是解决方案吗?当然,稍后我必须检查结果是赋值还是零。

    我同意(如上所述) testResultObject(x:TClassA):布尔值

    3 回复  |  直到 7 年前
        1
  •  9
  •   David Heffernan    7 年前

    您的代码存在严重问题。在出现错误的情况下,它会接受异常,并返回无效的对象引用。

    function testResultObject: TClassA;
    begin
      Result := TClassA.Create;    
      try
        Result.DoSomething;
      except
        Result.Free;
        raise;
      end;
    end;
    

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

    obj := testResultObject;
    try
      // do things with obj
    finally
      obj.Free;
    end;
    
        2
  •  5
  •   Disillusioned    7 年前

    第二种方法可行,但有两个严重问题。

    • 通过吞下所有的例外,(正如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;
    

    上述create函数最重要的优点是:就任何调用方/客户端代码而言, 它的行为与正常的TObject完全相同。创造 .
    因此 对的

    注意,我不喜欢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;
    


    撇开以上所有因素不谈,如果你的工作不认真,犯粗心错误的可能性会小得多 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;
    
        3
  •  4
  •   J...    7 年前

    唯一的问题是:

     function testResultObject: TClassA;
     begin
       Result := TClassA.Create;    
       try
         Result.DoSomething;
       except
         Result.Free;
       end;
     end;
    

    就是你无法知道这个功能是否成功。释放对象不会改变引用;变量仍将指向(现在)对象曾经存在的无效内存位置。必须将引用显式设置为 nil )然后你需要做:

    try
      Result.DoSomething;
    except
      FreeAndNil(Result);
    end;
    

    (使用 Assigned 或其他)如您所愿。然而,这仍然不是一种非常干净的方法,因为您仍然在接受异常。另一种解决方案可能是简单地引入一个新的构造函数或修改现有的构造函数。例如

    TFoo = class
      public
        constructor Create(ADoSomething : boolean = false);
        procedure DoSomething;
    end;
    
    constructor TClassA.Create(ADoSomething: Boolean = False);
    begin
      inherited Create;
      if ADoSomething then DoSomething;
    end;
    
    procedure TClassA.DoSomething;
    begin
      //
    end;
    

    function testResultObject: TClassA;
    begin
      Result := TClassA.Create(true);     
    end;
    

    DoSomething 执行到构造函数中,任何异常都会自动调用析构函数,您的内存管理问题就会消失。其他答案也有很好的解决方案。