除this问题外,我还对docwiki进行了一些测试和研究。我的结论是这种代码应该没有内存泄漏:
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
会更好。我只是想知道返回班级的功能方式是否可以像我写的那样修复。
答案 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;
答案 1 :(得分:5)
你的第二种方法有效,但有两个严重的问题。
我建议您对第二种方法进行以下改进:
{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;
答案 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
执行推送到构造函数中,因此任何异常都会自动调用析构函数,并且您的内存管理问题也会消失。其他答案也有很好的解决方案。