Is it memory safe to provide an object as a function result?

后端 未结 8 1999
旧巷少年郎
旧巷少年郎 2021-01-06 21:05

Here I provide simple piece of code.

function GetStringList:TStringList;
var i:integer;
begin
   Result:=TStringList.Create;
   Result.Add(\'Adam\');
   Res         


        
8条回答
  •  被撕碎了的回忆
    2021-01-06 21:50

    These are the very kinds of questions I grappled with in my early days of Delphi. I suggest you take your time with it:

    • write test code with debug output
    • trace your code step-by-step
    • try different options and code constructs
    • and make sure you understand the nuances properly;

    The effort will prove a great help in writing robust code.

    Some comments on your sample code...

    1. You should get into the habit of always using resource protection in your code, even in simple examples; and especially since your question pertains to memory (resource) protection.
    2. If you name a function GetXXX, then there's no reason for anyone to suspect that it's going to create something, and they're unlikely to protect the resource. So careful naming of methods is extremely important.
    3. Whenever you call a method that creates something, assume it's your responsibility to destroy it.
    4. I noticed some code that would produce Hints from the compiler. I recommend you always eliminate ALL Hints & Warnings in your programs.
      • At best a Hint just means some arbitrary redundant code (excesses of which make maintenance more difficult). More likely it implies you haven't finished something, or rushed it and haven't finished testing/checking.
      • A Warning should always be taken seriously. Even though sometimes the compiler's concern is a logical impossibility in the specific situation, the warning may indicate some subtle language nuance that you're not aware of. The code can always be rewritten in a more robust fashion.
      • I have seen many examples of poor resource protection where there is a compiler warning giving a clue as to the problem. So check them out, it will aid in the learning.

    If an exception is raised in a method that returns a new object, care should be taken to ensure there isn't a memory leak as a result.

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

    A better name for the following would be PopulateStringList or LoadStringList. Again, resource protection is required, but there is a simpler option as well.

    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;
    

    However; in the above code, creating a temporary stringlist is overkill when you could just add the strings directly to the input object.
    Depending on how the input stringlist is used, it is usually advisable to enclose a BeginUpdate/EndUpdate so that the changes can be handled as a batch (for performance reasons). If your method is general purpose, then you have no idea of the origin of the input, so you should definitely take the precaution.

    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;
    

    our original code below had a memory leak because it called a method to create an object, but did not destroy. However, because the method that created the object was called GetStringList, the error is not immediately obvious.

    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;
    

    The only error in your final snippet was the lack of resource protection. The technique used is quite acceptable, but may not be ideally suited to all problems; so it helps to also be familiar with the previous technique.

    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;
    

提交回复
热议问题