Delphi函数返回类对象
除了问题,我还对docwiki进行了一些测试和研究。我的结论是,这种代码应该在没有内存泄漏的情况下工作:Delphi函数返回类对象,delphi,Delphi,除了问题,我还对docwiki进行了一些测试和研究。我的结论是,这种代码应该在没有内存泄漏的情况下工作: function testResultObject: TClassA; begin Result := TClassA.Create; Result.DoSomething; end; 然后我可以用这种方式调用上面的代码: var k: TClassA; begin k := testResultObject; try //code code code finally
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;
正如Remy在回答中所建议的那样,最好避免这种做法,而是使用类似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
会更好。我只是想知道是否可以像我写的那样修复return-a-class函数方式。唯一的问题是:
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
执行推送到构造函数中,因此任何异常都会自动调用析构函数,您的内存管理问题就会消失。其他答案也有很好的解决方案。您的代码存在严重问题。在发生错误的情况下,它会接受异常,并返回无效的对象引用
这很容易解决。标准方法如下:
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;
第二种方法可行,但有两个严重问题
- 通过吞咽所有的异常(正如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完全相同。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;
就我个人而言,我一直使用第二种方法(真/假返回),它更好,并且使代码更具可读性。您的解决方案很好,但您还应该在异常块中添加一个结果:=nil您误解了我先前的答案。在修改对象时(在该示例中,将项添加到列表中),将对象作为参数传递是很好的。这是一个完全不同的场景。正如我所说的,我更喜欢testResultObject(x:TClassA):布尔而不是返回TClassA。我只是想知道我的解决方案是否正确,仅此而已;)我同意你上次的实施是最好的@拉斐尔:你为什么喜欢布尔型呢。强制用户进行测试,并接受异常。有一种简单的方法可以解决这个问题,并使调用代码遵循标准模式。@Craig您的最终建议并不能解决函数在实例化对象之前可能引发异常的情况。这就是你的第一种方法获胜的地方。@RaffaeleRossi好奇心总是值得称赞的!:)顺便说一句,我想对返回布尔结果的函数提出几点警告:1)确保您始终清楚True vs False的含义。有时候真的没有足够好的理由去区分;然后它真的应该是一个过程。2) 记住在呼叫者中检查结果。这是一件容易忘记的事情。是的,这是一个假设的情景。把它分开可能是好的,但增加了调用者正确处理的责任。在函数中封装将删除该选项。例如,可能会有许可证检查。我不明白为什么你会建议接受这样的例外情况that@DavidHeffernan我不建议这样做,这就是为什么我添加了一个更明智的选择。OP提出了这一模式,似乎表明他们很清楚这一模式的缺点,所以我觉得没有必要重复这一点