如何从Delphi中的函数返回一个对象而不会导致访问冲突?

时间:2009-02-18 14:33:08

标签: delphi

我有一个返回TStringList的delphi函数,但是当我返回一个值并尝试使用它时,我得到了一个访问冲突错误,即

myStringList := FuncStringList();
myStringList.Items.Count   // <-- This causes an access violation

// function FuncStringList
function FuncStringList:TStringList;
var
  vStrList:TStringList;
begin

  vStrList := TStringList.Create;
   ...
  // Fill the vStrList

  Result := vStrList 
  vStrList.Free;    //<- when i free here, this function will cause AccessViolation
end;

如何返回TStringList并仍在本地函数中释放它?

10 个答案:

答案 0 :(得分:28)

正如Smasher所说,你无法释放它;调用返回对象的函数的代码负责销毁它。

顺便说一下,这是糟糕的代码设计,因为它让谁分配和释放会让人感到困惑。更好的方法是让调用者创建对象并将其传递给函数。这样,创建它的代码也可以释放它。像这样:

var
  SL: TStringList;
begin
  SL := TStringList.Create;
  try
    ProcToFillStringList(SL);
    //Do something with populated list
  finally
    SL.Free;
  end;
end;

// Note I've made the parameter a TStrings and not a TStringList. This allows
// passing a TMemo.Lines or a TListBox or TComboBox Items as well.
procedure ProcToFillStringList(const SList: TStrings);
  // Do whatever populates the list with SList.Add()
end;

现在对于谁做了什么没有混淆 - 创建对象的相同代码负责释放它。而IMO的代码更易于阅读和维护。

答案 1 :(得分:27)

  

如何返回TStringList并仍在本地函数中释放它?

你做不到。如果在本地函数中释放它,则无法使用返回值。结果和vStrList指向内存中的相同TStringList对象。 TStringList是一个类和

Result := vStrList
因此,

不会复制字符串列表,只会复制引用。

所以,你应该在完成它之后释放调用上下文中的字符串列表,或者将字符串列表作为参数传递给你的函数,就像这样

procedure FuncStringList (StringList : TStringList);

让调用代码创建并释放字符串列表。正如其他答案所指出的那样,这是更好的方式,因为它使所有权非常明确。

答案 2 :(得分:6)

简单回答:你做不到。你为什么要这样做?是因为你已经知道你需要释放你在创建它们的同一个函数中创建的每个对象吗?这通常是正确的,但并非总是如此,这是规则的例外之一。更好的方法是每个对象必须由其所有者释放。

如果你有一个生成一个对象的函数,比如这个函数,但是然后将它传递给另一个函数,它就不会拥有该对象的所有权。删除免费调用并记录它,因此您(以及使用此功能的任何其他人)将意识到它创建了一个新对象,调用它的代码必须拥有所有权。

答案 3 :(得分:3)

简单回答(带示例):

当你这样做时

  

结果:= vStrList

将vStrList分配给Result。此时vStrList和Result 是相同的!所以,在下一行代码中,当你释放vStrList时,你也释放了结果(好吧,这不是技术上准确但我用它来保持解释简单)。这就是当您尝试使用该功能的结果时获得AV的原因。释放vStrList时,结果被销毁。

所以,这将解决您的问题:

function FuncStringList:TStringList;
begin
  Result := TStringList.Create;
  // Do stuff with Result
  // never free (here, in this function) the Result
end;

FuncStringList的调用者必须释放“结果”。

你这样称呼它:

myStringList := FuncStringList;
try 
  myStringList.Items.Count                      
finally
  FreeAndNil(myStringList);    <------------- NOW you can free "Result"
end;

另一个例子:

function makelist: tstringlist;
begin
  result := Tstringlist.create;
  result.add('1');
  result.add('2');
end;

procedure TForm1.Button_GOOD_Click(Sender: TObject);
var list : tstringlist;
begin
  list := makelist;
  DoStuff(list);
  list.free;      //ok
end;

procedure TForm1.Button_BAD_Click(Sender: TObject);
begin
  listbox1.items.Assign(makelist);  // <---- memory leak here because you forgot to free
end; 

我把这个说明放在这里,然后才会有人开始“挑选”我的解释。我在我的解释中使用了一些“快捷方式”,以避免复杂的概念(例如指针赋值)使事情变得非常简单。 @gath问了一个基本问题,这意味着他仍然在学习编程的基础知识。

答案 4 :(得分:2)

你根本无法释放某些东西,然后期望在以后引用它。这是错误的方式。您有两个基本选项:

  • 不要免费拨打电话,并让来电者负责处理对象
  • 让调用者传入一个对象,使其负责创建和免费

第一个选项看起来更简单,保持函数接口更小等。第二个选项使得使用不易出错,因为调用者直接负责管理对象。

答案 5 :(得分:0)

在完成对方法的调用之前,请不要释放对象。您当前正在对已销毁的对象调用Count方法,因此会出错。

为什么不在调用函数中创建字符串列表,并将其引用传递给填充它的方法?或者让字符串列表成为类的成员,并在释放拥有它的类时释放它?

答案 6 :(得分:0)

另一种可能性是使用动态数组而不是TStringList。由于数组是引用计数的,因此您永远不必担心将其释放。

答案 7 :(得分:0)

我遇到过这种情况的策略是通过text属性传递stringlist内容,并将返回的字符串传递给函数。这样就没有必要讨论谁发布了谁。当然,你必须做更多的编码,但它更安全。 这个例子是对Ken White的改编:

var
  SL: TStringList;
  Aux: String;
begin
  SL := TStringList.Create;
  try
    SL.Text := ProcToFillStringList;
    //Do something with populated list
  finally
    SL.Free;
  end;
end;

 // It receives a default param, in the case you have to deal with 
 // StringList with some previous content    
 function ProcToFillStringList(SListContent: String = ''):String;
 // Do the stuff you need to do with the content
end;

例外情况是当你拥有的只是对象时,无法通过安全类型(在本例中为字符串)检索其中的内容;然后我跟随肯·怀特的想法。

答案 8 :(得分:0)

两者都指的是相同的记忆,如果你释放它们都会被释放.......

答案 9 :(得分:0)

作为Out变量。

function GetList(Parameter1: string; out ResultList: TStringList): boolean;
begin
  // either
  if not Assigned(ResultList) then
    raise Exception.Create('Out variable is not declared.');
  // or
  Result := False;
  if not Assigned(ResultList) then
    Exit;
  ResultList.Clear;
  ResultList.Add('Line1');
  ResultList.Add('Line2');
  //...
  Result := True;
end;

或作为字符串。

function GetList(Parameter1: string): string;
var
  slList: TStringList;
begin
  slList := TStringList.Create;
  try
    slList.Clear;
    slList.Add('Line1');
    slList.Add('Line2');
    //...
    Result := slList.Text;
  finally
    slList.Free;
  end;
end;

procedure Main;
var
  slList: TStringList;
begin
  slList := TStringList.Create;
  try
    // either
    GetList(Parameter1, slList);
    // or
    slList.Text := GetList(Parameter1);
    // process slList...
  finally
    slList.Free;
  end;
end;