这里我提供简单的代码。
function GetStringList:TStringList;
var i:integer;
begin
Result:=TStringList.Create;
Result.Add('Adam');
Result.Add('Eva');
Result.Add('Kain');
Result.Add('Abel');
end;
procedure ProvideStringList(SL:TStringList);
var i:integer;
Names:TStringList;
begin
Names:=TStringList.Create;
Names.Add('Adam');
Names.Add('Eva');
Names.Add('Kain');
Names.Add('Abel');
SL.Assign(Names);
Names.Free;
end;
procedure TForm1.btn1Click(Sender: TObject);
var SL:TStringList;
i:integer;
begin
SL:=TStringList.Create;
SL.Assign(GetStringList);
for i:=0 to 3 do ShowMessage(SL[i]);
SL.Free;
end;
procedure TForm1.btn2Click(Sender: TObject);
var SL:TStringList;
i:integer;
begin
SL:=TStringList.Create;
ProvideStringList(SL);
for i:=0 to 3 do ShowMessage(SL[i]);
SL.Free;
end;
现在的问题是:函数GetStringList中的结果对象会发生什么:Tstringlist,它被创建但从未被释放? (我叫2次创建,只有1次免费) 是按照功能提供对象是安全的,还是应该使用过程来执行此任务,只需处理对象创建和销毁(过程ProvideStringlist)?我叫2次创建和2次免费。 还是有另一种解决方案吗?
提前完成
Lyborko
答案 0 :(得分:7)
将对象作为函数结果提供内存是否安全?
有可能,但需要注意执行者和电话。
例如:
function CreateBibleNames: TStrings;
begin
Result := TStringList.Create;
try
Result.Add('Adam');
Result.Add('Eva');
Result.Add('Kain');
Result.Add('Abel');
except
Result.Free;
raise;
end;
end;
但在Delphi中,最常见的模式是:
procedure GetBibleNames(Names: TStrings);
begin
Names.BeginUpdate;
try
//perhaps a Names.Clear here
//but I don't use it often because the other
//way is more flexible for the caller
Names.Add('Adam');
Names.Add('Eva');
Names.Add('Kain');
Names.Add('Abel');
finally
Names.EndUpdate;
end;
end;
所以调用者代码可能如下所示:
procedure TForm1.btn1Click(Sender: TObject);
var
Names: TStrings;
i:integer;
begin
Names := CreateBibleNames;
try
for i := 0 to Names.Count -1 do
ShowMessage(Names[i]);
finally
Names.Free;
end;
end;
和另一个更常见的版本:
procedure TForm1.btn1Click(Sender: TObject);
var
Names: TStrings;
i:integer;
begin
Names := TStringList.Create;
try
GetBibleNames(Names);
for i := 0 to Names.Count -1 do
ShowMessage(Names[i]);
finally
Names.Free;
end;
end;
(目前我没有编译器,所以可能存在一些错误)
答案 1 :(得分:5)
我不知道你的意思是安全的,但这是常见的做法。该函数的调用者负责释放返回的对象:
var
s : TStringList;
begin
s := GetStringList;
// stuff
s.free;
end;
答案 2 :(得分:3)
内存安全是类型安全的更严格变体。为了安全起见,您通常需要一个精确的垃圾收集器和一个类型系统来防止某些类型的类型转换和指针算术。通过这个指标,无论你是否编写返回对象的函数,Delphi都不是内存安全的。
答案 3 :(得分:2)
这些是我在德尔福早期遇到的各种问题。我建议你花点时间:
这项努力将有助于编写健壮的代码。
您对示例代码的一些评论...
如果在返回新对象的方法中引发异常,则应注意确保结果没有内存泄漏。
//function GetStringList:TStringList;
function CreateStringList:TStringList; //Rename method lest it be misinterpreted.
//var i: Integer; You don't use i, so why declare it? Get rid of it and eliminate your Hints and Warnings!
begin
Result := TStringList.Create;
try //Protect the memory until this method is done; as it can **only** be done by **this** method!
Result.Add('Adam');
Result.Add('Eva');
Result.Add('Kain');
Result.Add('Abel');
except
Result.Destroy; //Note Destroy is fine because you would not get here if the line: Result := TStringList.Create; failed.
raise; //Very important to re-raise the exception, otherwise caller thinks the method was successful.
end;
end;
以下更好的名称是PopulateStringList
或LoadStringList
。同样,需要资源保护,但也有一个更简单的选择。
procedure ProvideStringList(SL:TStringList);
var //i:integer; You don't use i, so why declare it? Get rid of it and eliminate your Hints and Warnings!
Names:TStringList;
begin
Names:=TStringList.Create;
try //ALWAYS protect local resources!
Names.Add('Adam');
Names.Add('Eva');
Names.Add('Kain');
Names.Add('Abel');
SL.Assign(Names);
finally //Finally is the correct choice here
Names.Free; //Destroy would also be okay.
end;
end;
然而;在上面的代码中,当你可以直接将字符串添加到输入对象时,创建临时字符串列表是过度的 根据输入字符串列表的使用方式,通常建议将BeginUpdate / EndUpdate括起来,以便可以批量处理更改(出于性能原因)。如果你的方法是通用的,那么你不知道输入的来源,所以你一定要采取预防措施。
procedure PopulateStringList(SL:TStringList);
begin
SL.BeginUpdate;
try //YES BeginUpdate must be protected like a resource
SL.Add('Adam');
SL.Add('Eva');
SL.Add('Kain');
SL.Add('Abel');
finally
SL.EndUpdate;
end;
end;
我们下面的原始代码有内存泄漏,因为它调用了一个创建对象的方法,但没有销毁。但是,因为创建对象的方法称为获取 StringList,所以错误不会立即显现。
procedure TForm1.btn1Click(Sender: TObject);
var SL:TStringList;
i:integer;
begin
//SL:=TStringList.Create; This is wrong, your GetStringList method creates the object for you.
//SL.Assign(GetStringList);
SL := CreateStringList; //I also used the improved name here.
try //Don't forget resource protection.
for i:=0 to 3 do ShowMessage(SL[i]);
finally
SL.Free;
end;
end;
最终片段中唯一的错误是缺乏资源保护。使用的技术是完全可以接受的,但可能不适合所有问题;因此,熟悉以前的技术也很有帮助。
procedure TForm1.btn2Click(Sender: TObject);
var SL:TStringList;
i:integer;
begin
SL:=TStringList.Create;
try //Be like a nun (Get in the habit)
ProvideStringList(SL);
for i:=0 to 3 do ShowMessage(SL[i]);
finally
SL.Free;
end;
end;
答案 4 :(得分:0)
这是泄漏的用法,而不是构造本身。
var sl2 : TStringlist;
sl2:=GetStringList;
sl.assign(sl2);
sl2.free;
非常好,甚至更容易,
sl:=getstringlist;
// no assign, thus no copy, one created one freed.
sl.free;
答案 5 :(得分:0)
不,它不是“内存安全”。当你创建一个对象时,有人必须释放它。
你的第一个例子泄漏了内存:
SL:=TStringList.Create;
SL.Assign(GetStringList); // <-- The return value of GetStringList is
// used, but not freed.
for i:=0 to 3 do ShowMessage(SL[i]);
SL.Free;
第二个示例工作正常,但您不必创建和释放其他临时实例(Names
)
一般来说,第二个例子略胜一筹,因为很明显,谁负责创建和销毁列表。 (调用者)在其他情况下,调用者必须释放返回的对象,或者禁止它。你无法从代码中分辨出来。如果必须这样做,最好相应地命名方法。 (CreateList
优于GetList
)。
答案 6 :(得分:0)
我使用两种成语的组合。将对象作为可选参数传递,如果未传递,则创建对象。在任何一种情况下都将对象作为函数结果返回。
该技术具有(1)在被调用函数内部创建对象的灵活性,以及(2)将对象作为参数传递的调用者的调用者控制。控制有两个含义:控制所用对象的实际类型,并控制何时释放对象。
这段简单的代码就是这个习惯用语的例证。
function MakeList(aList:TStrings = nil):TStrings;
var s:TStrings;
begin
s:=aList;
if s=nil then
s:=TSTringList.Create;
s.Add('Adam');
s.Add('Eva');
result:=s;
end;
以下是三种不同的使用方法
最简单的用法,用于快速和脏代码
var sl1,sl2,sl3:TStrings;
sl1:=MakeList;
当程序员想要更明确的所有权和/或使用自定义类型时
sl2:=MakeList(TMyStringsList.create);
以前创建对象时
sl3:=TMyStringList.Create;
....
MakeList(sl3);
答案 7 :(得分:-1)
在 btn1Click 中,你应该这样做:
var sl2: TStringList;
sl2 := GetStringList:
SL.Assign(sl2);
sl2.Free;
在 btn2Click 中,在调用 ProvideStringList 以不创建内存泄漏之前,您不必创建SL实例。