提供对象作为函数结果是否安全?

时间:2009-12-12 17:59:57

标签: delphi

这里我提供简单的代码。

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

8 个答案:

答案 0 :(得分:7)

  

将对象作为函数结果提供内存是否安全?

有可能,但需要注意执行者和电话。

  1. 为调用者清楚,他控制返回对象的生命周期
  2. 当功能失效时,请确保您没有内存泄漏。
  3. 例如:

    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)

这些是我在德尔福早期遇到的各种问题。我建议你花点时间:

  • 使用调试输出编写测试代码
  • 逐步跟踪您的代码
  • 尝试不同的选项和代码构造
  • 并确保您正确理解细微差别;

这项努力将有助于编写健壮的代码。

您对示例代码的一些评论...

  1. 您应该养成在代码中始终使用资源保护的习惯,即使在简单的示例中也是如此;并且特别是,因为您的问题与内存(资源)保护有关。
  2. 如果您将功能命名为获取 XXX,则没有理由让任何人怀疑它会创建某些内容,并且他们不太可能保护资源。因此,仔细命名方法非常重要。
  3. 每当您调用创建某事的方法时,假设您有责任销毁它。
  4. 我注意到一些会从编译器生成Hints的代码。我建议你永远消除所有提示&程序中的警告。
    • 最好的提示只是意味着一些任意的冗余代码(过多的代码会使维护变得更加困难)。更可能的是,它意味着你没有完成某些事情,或者没有完成测试/检查。
    • 警告应该始终认真对待。即使有时编译器的关注在特定情况下是逻辑上不可能的,但警告可能表明您不了解某些微妙的语言细微差别。代码可以始终以更健壮的方式重写。
    • 我见过许多资源保护不佳的例子,其中有一个编译器警告提供了问题的线索。所以检查出来,它将有助于学习。
  5. 如果在返回新对象的方法中引发异常,则应注意确保结果没有内存泄漏。

    //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;
    

    以下更好的名称是PopulateStringListLoadStringList。同样,需要资源保护,但也有一个更简单的选择。

    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实例。