2017-08-25 98 views
4

除了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): boolean会更好。我只是想知道是否可以像我写的那样修复返回类功能的方式。

+2

就我个人而言,我一直使用第二种方法(真/假返回),它更好,使代码更具可读性。你的解决方案很好,但你应该在异常块中添加一个结果:= nil –

+0

你误解了我以前的答案。当您修改该对象时(在该示例中,将项目添加到列表中),传递对象作为参数是很好的。这是一个完全不同的场景。 –

回答

9

您的代码存在严重问题。如果发生错误,它会吞服该异常,并返回无效的对象引用。

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

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

我认为这是最好的答案。我喜欢用标准的方式来做事,在这里我可以使用普通的试用版。此外,该功能正在做清理和代码是很容易阅读。 –

4

与此唯一的问题:

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

是,你有没有办法知道函数是否成功的方式。释放对象不会改变引用;该变量仍将指向该对象过去存在的(现在)无效内存位置。如果您希望使用者能够测试引用是否有效,则必须明确地将引用设置为nil。如果你想使用这个模式(具有nil消费者测试),那么你需要做的:

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

这样调用者可以测试结果为nil(使用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执行到构造任何异常,自然会自动调用析构函数,你的内存管理问题就会消失。其他答案也有很好的解决方案。

+0

我不明白为什么你会建议吞下这样的例外 –

+0

@DavidHeffernan我不会建议它,这就是为什么我添加了一个更明智的选择。 OP建议了这种模式,似乎表明他们很清楚它的缺点,所以我不觉得有必要说清楚这一点。 –

+0

我怀疑Raffaele是否赞赏这一点。在专注于泄漏的问题中没有提及这一点。当我读到它时,你的答案的前半部分会祝福异常吞咽。这只是错误的。很容易修复。 –

5

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

  • 通过吞咽所有异常(如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; 

上面创建功能的最重要的好处是:只要任何调用/客户端代码而言,它表现完全像正常的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; 

注:需要注意的是,你不能永远有你的来电者根本无视内存管理是非常重要的;这将我带到下一部分。


所有上述之外,还有更使粗心的错误,如果你的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; 
+0

正如我所说,我更喜欢像testResultObject(x:TClassA):布尔型,而不是返回TClassA。我只是想知道我的解决方案是否正确,没有更多;)我同意你最后的实现是最好的! –

+1

@Raffaele你为什么喜欢布尔值。强制用户测试它,并吞咽异常。有这样一个简单的方法来解决这个问题,并使你的调用代码遵循标准模式。 –

+0

@Craig你的最终建议并不能解决函数在实例化对象之前可能引发异常的场景。这是你第一种方法获胜的地方。 –